Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-dev
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 74420e7..d79edb4 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -91,6 +91,8 @@
compressible: true,
androidManifest: "AndroidManifest.xml",
+
+ compat_configs: ["connectivity-platform-compat-config"],
}
apex_key {
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index c798580..e382713 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -65,8 +65,9 @@
skb->pkt_type == PACKET_MULTICAST;
}
-// try to make the first 'len' header bytes readable via direct packet access
-static inline __always_inline void try_make_readable(struct __sk_buff* skb, int len) {
+// try to make the first 'len' header bytes readable/writable via direct packet access
+// (note: AFAIK there is no way to ask for only direct packet read without also getting write)
+static inline __always_inline void try_make_writable(struct __sk_buff* skb, int len) {
if (len > skb->len) len = skb->len;
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index 8971f6b..9a9d337 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -57,7 +57,7 @@
// Not clear if this is actually necessary considering we use DPA (Direct Packet Access),
// but we need to make sure we can read the IPv6 header reliably so that we can set
// skb->mark = 0xDeadC1a7 for packets we fail to offload.
- try_make_readable(skb, l2_header_size + sizeof(struct ipv6hdr));
+ try_make_writable(skb, l2_header_size + sizeof(struct ipv6hdr));
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -224,7 +224,7 @@
if (skb->protocol != htons(ETH_P_IP)) return TC_ACT_PIPE;
// Possibly not needed, but for consistency with nat64 up above
- try_make_readable(skb, sizeof(struct iphdr));
+ try_make_writable(skb, sizeof(struct iphdr));
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 977e918..92a774c 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -122,7 +122,7 @@
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
// Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
+ try_make_writable(skb, l2_header_size + IP6_HLEN + TCP_HLEN);
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
@@ -369,7 +369,7 @@
// not trigger and thus we need to manually make sure we can read packet headers via DPA.
// Note: this is a blind best effort pull, which may fail or pull less - this doesn't matter.
// It has to be done early cause it will invalidate any skb->data/data_end derived pointers.
- try_make_readable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
+ try_make_writable(skb, l2_header_size + IP4_HLEN + TCP_HLEN);
void* data = (void*)(long)skb->data;
const void* data_end = (void*)(long)skb->data_end;
diff --git a/framework/Android.bp b/framework/Android.bp
index 3703df8..d7de439 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -92,6 +92,7 @@
"modules-utils-preconditions",
],
libs: [
+ "app-compat-annotations",
"framework-connectivity-t.stubs.module_lib",
"unsupportedappusage",
],
@@ -152,6 +153,11 @@
],
}
+platform_compat_config {
+ name: "connectivity-platform-compat-config",
+ src: ":framework-connectivity",
+}
+
cc_library_shared {
name: "libframework-connectivity-jni",
min_sdk_version: "30",
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 99f48b4..8782b33 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -19,12 +19,16 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.LinkPropertiesUtils;
import java.net.Inet4Address;
@@ -38,6 +42,7 @@
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.stream.Collectors;
/**
* Describes the properties of a network link.
@@ -52,6 +57,17 @@
*
*/
public final class LinkProperties implements Parcelable {
+ /**
+ * The {@link #getRoutes()} now can contain excluded as well as included routes. Use
+ * {@link RouteInfo#getType()} to determine route type.
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) // Switch to S_V2 when it is available.
+ @VisibleForTesting
+ public static final long EXCLUDED_ROUTES = 186082280;
+
// The interface described by the network link.
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mIfaceName;
@@ -738,10 +754,25 @@
/**
* Returns all the {@link RouteInfo} set on this link.
*
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @return An unmodifiable {@link List} of {@link RouteInfo} for this link.
*/
public @NonNull List<RouteInfo> getRoutes() {
- return Collections.unmodifiableList(mRoutes);
+ if (CompatChanges.isChangeEnabled(EXCLUDED_ROUTES)) {
+ return Collections.unmodifiableList(mRoutes);
+ } else {
+ return Collections.unmodifiableList(getUnicastRoutes());
+ }
+ }
+
+ /**
+ * Returns all the {@link RouteInfo} of type {@link RouteInfo#RTN_UNICAST} set on this link.
+ */
+ private @NonNull List<RouteInfo> getUnicastRoutes() {
+ return mRoutes.stream()
+ .filter(route -> route.getType() == RouteInfo.RTN_UNICAST)
+ .collect(Collectors.toList());
}
/**
@@ -757,11 +788,14 @@
/**
* Returns all the routes on this link and all the links stacked above it.
+ *
+ * Only unicast routes are returned for apps targeting Android S or below.
+ *
* @hide
*/
@SystemApi
public @NonNull List<RouteInfo> getAllRoutes() {
- List<RouteInfo> routes = new ArrayList<>(mRoutes);
+ final List<RouteInfo> routes = new ArrayList<>(getRoutes());
for (LinkProperties stacked: mStackedLinks.values()) {
routes.addAll(stacked.getAllRoutes());
}
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index b28c006..0d2b620 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -24,6 +24,8 @@
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.modules.utils.build.SdkLevel;
+
import java.util.Objects;
/**
@@ -473,6 +475,9 @@
@NonNull
@SystemApi(client = MODULE_LIBRARIES)
public Builder setLocalRoutesExcludedForVpn(boolean excludeLocalRoutes) {
+ if (!SdkLevel.isAtLeastT()) {
+ throw new UnsupportedOperationException("Method is not supported");
+ }
mConfig.excludeLocalRouteVpn = excludeLocalRoutes;
return this;
}
diff --git a/service/src/com/android/server/TestNetworkService.java b/service/src/com/android/server/TestNetworkService.java
index a0bfb4a..ccc2776 100644
--- a/service/src/com/android/server/TestNetworkService.java
+++ b/service/src/com/android/server/TestNetworkService.java
@@ -231,6 +231,7 @@
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED);
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
nc.setNetworkSpecifier(new TestNetworkSpecifier(iface));
nc.setAdministratorUids(administratorUids);
if (!isMetered) {
diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java
index 2885ba7..62b3add 100755
--- a/service/src/com/android/server/connectivity/PermissionMonitor.java
+++ b/service/src/com/android/server/connectivity/PermissionMonitor.java
@@ -127,9 +127,17 @@
@GuardedBy("this")
private final Set<Integer> mUidsAllowedOnRestrictedNetworks = new ArraySet<>();
+ // Store PackageManager for each user.
+ // Keys are users, Values are PackageManagers which get from each user.
@GuardedBy("this")
private final Map<UserHandle, PackageManager> mUsersPackageManager = new ArrayMap<>();
+ // Store appIds traffic permissions for each user.
+ // Keys are users, Values are SparseArrays where each entry maps an appId to the permissions
+ // that appId has within that user.
+ @GuardedBy("this")
+ private final Map<UserHandle, SparseIntArray> mUsersTrafficPermissions = new ArrayMap<>();
+
private static final int SYSTEM_APPID = SYSTEM_UID;
private static final int MAX_PERMISSION_UPDATE_LOGS = 40;
@@ -292,14 +300,24 @@
sendUidsNetworkPermission(uids, true /* add */);
}
- private void updateAppIdsTrafficPermission(final SparseIntArray appIds,
- final SparseIntArray extraAppIds) {
- for (int i = 0; i < extraAppIds.size(); i++) {
- final int appId = extraAppIds.keyAt(i);
- final int permission = extraAppIds.valueAt(i);
- appIds.put(appId, appIds.get(appId) | permission);
+ /**
+ * Calculates permissions for appIds.
+ * Maps each appId to the union of all traffic permissions that the appId has in all users.
+ *
+ * @return The appIds traffic permissions.
+ */
+ private synchronized SparseIntArray makeAppIdsTrafficPermForAllUsers() {
+ final SparseIntArray appIds = new SparseIntArray();
+ // Check appIds permissions from each user.
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userAppIds = mUsersTrafficPermissions.get(user);
+ for (int i = 0; i < userAppIds.size(); i++) {
+ final int appId = userAppIds.keyAt(i);
+ final int permission = userAppIds.valueAt(i);
+ appIds.put(appId, appIds.get(appId) | permission);
+ }
}
- sendAppIdsTrafficPermission(appIds);
+ return appIds;
}
private SparseIntArray getSystemTrafficPerm() {
@@ -363,6 +381,10 @@
// mUidsAllowedOnRestrictedNetworks.
updateUidsAllowedOnRestrictedNetworks(mDeps.getUidsAllowedOnRestrictedNetworks(mContext));
+ // Read system traffic permissions when a user removed and put them to USER_ALL because they
+ // are not specific to any particular user.
+ mUsersTrafficPermissions.put(UserHandle.ALL, getSystemTrafficPerm());
+
final List<UserHandle> usrs = mUserManager.getUserHandles(true /* excludeDying */);
// Update netd permissions for all users.
for (UserHandle user : usrs) {
@@ -487,9 +509,16 @@
final SparseIntArray uids = makeUidsNetworkPerm(apps);
updateUidsNetworkPermission(uids);
- // App ids traffic permission
- final SparseIntArray appIds = makeAppIdsTrafficPerm(apps);
- updateAppIdsTrafficPermission(appIds, getSystemTrafficPerm());
+ // Add new user appIds permissions.
+ final SparseIntArray addedUserAppIds = makeAppIdsTrafficPerm(apps);
+ mUsersTrafficPermissions.put(user, addedUserAppIds);
+ // Generate appIds from all users and send result to netd.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user added
+ mPermissionUpdateLogs.log("New user(" + user.getIdentifier() + ") added: nPerm uids="
+ + uids + ", tPerm appIds=" + addedUserAppIds);
}
/**
@@ -502,6 +531,7 @@
public synchronized void onUserRemoved(@NonNull UserHandle user) {
mUsers.remove(user);
+ // Remove uids network permissions that belongs to the user.
final SparseIntArray removedUids = new SparseIntArray();
final SparseIntArray allUids = mUidToNetworkPerm.clone();
for (int i = 0; i < allUids.size(); i++) {
@@ -512,6 +542,27 @@
}
}
sendUidsNetworkPermission(removedUids, false /* add */);
+
+ // Remove appIds traffic permission that belongs to the user
+ final SparseIntArray removedUserAppIds = mUsersTrafficPermissions.remove(user);
+ // Generate appIds from left users.
+ final SparseIntArray appIds = makeAppIdsTrafficPermForAllUsers();
+ // Clear permission on those appIds belong to this user only, set the permission to
+ // PERMISSION_UNINSTALLED.
+ if (removedUserAppIds != null) {
+ for (int i = 0; i < removedUserAppIds.size(); i++) {
+ final int appId = removedUserAppIds.keyAt(i);
+ // Need to clear permission if the removed appId is not found in the array.
+ if (appIds.indexOfKey(appId) < 0) {
+ appIds.put(appId, PERMISSION_UNINSTALLED);
+ }
+ }
+ }
+ sendAppIdsTrafficPermission(appIds);
+
+ // Log user removed
+ mPermissionUpdateLogs.log("User(" + user.getIdentifier() + ") removed: nPerm uids="
+ + removedUids + ", tPerm appIds=" + removedUserAppIds);
}
/**
@@ -598,6 +649,39 @@
}
}
+ private synchronized void updateAppIdTrafficPermission(int uid) {
+ final int appId = UserHandle.getAppId(uid);
+ final int uidTrafficPerm = getTrafficPermissionForUid(uid);
+ final SparseIntArray userTrafficPerms =
+ mUsersTrafficPermissions.get(UserHandle.getUserHandleForUid(uid));
+ if (userTrafficPerms == null) {
+ Log.wtf(TAG, "Can't get user traffic permission from uid=" + uid);
+ return;
+ }
+ // Do not put PERMISSION_UNINSTALLED into the array. If no package left on the uid
+ // (PERMISSION_UNINSTALLED), remove the appId from the array. Otherwise, update the latest
+ // permission to the appId.
+ if (uidTrafficPerm == PERMISSION_UNINSTALLED) {
+ userTrafficPerms.delete(appId);
+ } else {
+ userTrafficPerms.put(appId, uidTrafficPerm);
+ }
+ }
+
+ private synchronized int getAppIdTrafficPermission(int appId) {
+ int permission = PERMISSION_NONE;
+ boolean installed = false;
+ for (UserHandle user : mUsersTrafficPermissions.keySet()) {
+ final SparseIntArray userApps = mUsersTrafficPermissions.get(user);
+ final int appIdx = userApps.indexOfKey(appId);
+ if (appIdx >= 0) {
+ permission |= userApps.valueAt(appIdx);
+ installed = true;
+ }
+ }
+ return installed ? permission : PERMISSION_UNINSTALLED;
+ }
+
/**
* Called when a package is added.
*
@@ -607,9 +691,12 @@
* @hide
*/
public synchronized void onPackageAdded(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestPermissionForUid(uid, currentPermission, packageName);
@@ -633,10 +720,12 @@
// package can bypass VPN.
updateVpnUid(uid, true /* add */);
mAllApps.add(appId);
+
+ // Log package added.
mPermissionUpdateLogs.log("Package add: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
}
private int highestUidNetworkPermission(int uid) {
@@ -664,9 +753,12 @@
* @hide
*/
public synchronized void onPackageRemoved(@NonNull final String packageName, final int uid) {
+ // Update uid permission.
+ updateAppIdTrafficPermission(uid);
+ // Get the appId permission from all users then send the latest permission to netd.
final int appId = UserHandle.getAppId(uid);
- final int trafficPerm = getTrafficPermissionForUid(uid);
- sendPackagePermissionsForAppId(appId, trafficPerm);
+ final int appIdTrafficPerm = getAppIdTrafficPermission(appId);
+ sendPackagePermissionsForAppId(appId, appIdTrafficPerm);
// If the newly-removed package falls within some VPN's uid range, update Netd with it.
// This needs to happen before the mUidToNetworkPerm update below, since
@@ -680,10 +772,13 @@
final int currentPermission = mUidToNetworkPerm.get(uid, PERMISSION_NONE);
final int permission = highestUidNetworkPermission(uid);
+
+ // Log package removed.
mPermissionUpdateLogs.log("Package remove: name=" + packageName + ", uid=" + uid
+ ", nPerm=(" + permissionToString(permission) + "/"
+ permissionToString(currentPermission) + ")"
- + ", tPerm=" + permissionToString(trafficPerm));
+ + ", tPerm=" + permissionToString(appIdTrafficPerm));
+
if (permission != currentPermission) {
final SparseIntArray apps = new SparseIntArray();
int sdkSandboxUid = -1;
diff --git a/tests/common/Android.bp b/tests/common/Android.bp
index 7bb7cb5..509e881 100644
--- a/tests/common/Android.bp
+++ b/tests/common/Android.bp
@@ -36,6 +36,7 @@
"modules-utils-build",
"net-tests-utils",
"net-utils-framework-common",
+ "platform-compat-test-rules",
"platform-test-annotations",
],
libs: [
diff --git a/tests/common/AndroidTest_Coverage.xml b/tests/common/AndroidTest_Coverage.xml
index 7c8e710..d4898b2 100644
--- a/tests/common/AndroidTest_Coverage.xml
+++ b/tests/common/AndroidTest_Coverage.xml
@@ -18,6 +18,8 @@
</target_preparer>
<option name="test-tag" value="ConnectivityCoverageTests" />
+ <!-- Tethering/Connectivity is a SDK 30+ module -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.connectivity.tests.coverage" />
diff --git a/tests/common/java/android/net/LinkPropertiesTest.java b/tests/common/java/android/net/LinkPropertiesTest.java
index 4d85a57..8fc636a 100644
--- a/tests/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/common/java/android/net/LinkPropertiesTest.java
@@ -20,6 +20,7 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
import static com.android.testutils.ParcelUtils.parcelingRoundTrip;
@@ -30,6 +31,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.compat.testing.PlatformCompatChangeRule;
import android.net.LinkProperties.ProvisioningChange;
import android.os.Build;
import android.system.OsConstants;
@@ -45,6 +47,9 @@
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -65,6 +70,9 @@
@Rule
public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+ @Rule
+ public final PlatformCompatChangeRule compatChangeRule = new PlatformCompatChangeRule();
+
private static final InetAddress ADDRV4 = address("75.208.6.1");
private static final InetAddress ADDRV6 = address("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
private static final InetAddress DNS1 = address("75.208.7.1");
@@ -1254,6 +1262,7 @@
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
public void testRouteAddWithSameKey() throws Exception {
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan0");
@@ -1268,4 +1277,36 @@
lp.addRoute(new RouteInfo(v4, address("192.0.2.1"), "wlan0", RTN_THROW, 1460));
assertEquals(2, lp.getRoutes().size());
}
+
+ @Test @IgnoreUpTo(SC_V2)
+ @EnableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesEnabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(1, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 0), RTN_THROW));
+ assertEquals(2, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(GATEWAY1));
+ assertEquals(3, lp.getRoutes().size());
+ }
+
+ @Test @IgnoreUpTo(SC_V2)
+ @DisableCompatChanges({LinkProperties.EXCLUDED_ROUTES})
+ public void testExcludedRoutesDisabled() {
+ final LinkProperties lp = new LinkProperties();
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV4, 0), RTN_UNREACHABLE));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 5), RTN_THROW));
+ assertEquals(0, lp.getRoutes().size());
+
+ lp.addRoute(new RouteInfo(new IpPrefix(ADDRV6, 2), RTN_UNICAST));
+ assertEquals(1, lp.getRoutes().size());
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d40bc9f..9055861 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -590,6 +590,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedLinkPropertiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
@@ -675,6 +676,7 @@
}
@DevSdkIgnoreRule.IgnoreUpTo(SC_V2)
+ @AppModeFull(reason = "Cannot get installed packages in instant app mode")
@Test
public void testGetRedactedNetworkCapabilitiesForPackage() throws Exception {
final String groundedPkg = findPackageByPermissions(
diff --git a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
index 48cbd03..9590f88 100644
--- a/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
+++ b/tests/cts/net/src/android/net/cts/Ikev2VpnTest.java
@@ -30,6 +30,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -59,6 +60,7 @@
import androidx.test.InstrumentationRegistry;
import com.android.internal.util.HexDump;
+import com.android.networkstack.apishim.ConstantsShim;
import com.android.networkstack.apishim.Ikev2VpnProfileBuilderShimImpl;
import com.android.networkstack.apishim.Ikev2VpnProfileShimImpl;
import com.android.networkstack.apishim.VpnManagerShimImpl;
@@ -66,6 +68,7 @@
import com.android.networkstack.apishim.common.Ikev2VpnProfileShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.apishim.common.VpnManagerShim;
+import com.android.networkstack.apishim.common.VpnProfileStateShim;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -486,7 +489,14 @@
if (testSessionKey) {
// testSessionKey will never be true if running on <T
// startProvisionedVpnProfileSession() should return a non-null & non-empty random UUID.
- assertFalse(TextUtils.isEmpty(mVmShim.startProvisionedVpnProfileSession()));
+ final String sessionId = mVmShim.startProvisionedVpnProfileSession();
+ assertFalse(TextUtils.isEmpty(sessionId));
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTING, profileState.getState());
+ assertEquals(sessionId, profileState.getSessionId());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
} else {
sVpnMgr.startProvisionedVpnProfile();
}
@@ -502,6 +512,14 @@
final Network vpnNetwork = cb.expectCallback(CallbackEntry.AVAILABLE, anyNetwork())
.getNetwork();
+ if (testSessionKey) {
+ final VpnProfileStateShim profileState = mVmShim.getProvisionedVpnProfileState();
+ assertNotNull(profileState);
+ assertEquals(ConstantsShim.VPN_PROFILE_STATE_CONNECTED, profileState.getState());
+ assertFalse(profileState.isAlwaysOn());
+ assertFalse(profileState.isLockdownEnabled());
+ }
+
cb.expectCapabilitiesThat(vpnNetwork, TIMEOUT_MS, caps -> caps.hasTransport(TRANSPORT_VPN)
&& caps.hasCapability(NET_CAPABILITY_INTERNET)
&& !caps.hasCapability(NET_CAPABILITY_VALIDATED)
diff --git a/tests/native/Android.bp b/tests/native/Android.bp
index 9c286d8..a8d908a 100644
--- a/tests/native/Android.bp
+++ b/tests/native/Android.bp
@@ -9,8 +9,8 @@
"mts-tethering",
"vts",
],
+ test_config_template: "AndroidTestTemplate.xml",
min_sdk_version: "31",
- require_root: true,
tidy: false,
srcs: [
"connectivity_native_test.cpp",
@@ -30,5 +30,4 @@
"libutils",
],
compile_multilib: "first",
- defaults: ["connectivity-mainline-presubmit-cc-defaults"],
}
diff --git a/tests/native/AndroidTestTemplate.xml b/tests/native/AndroidTestTemplate.xml
new file mode 100644
index 0000000..44e35a9
--- /dev/null
+++ b/tests/native/AndroidTestTemplate.xml
@@ -0,0 +1,30 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Configuration for connectivity {MODULE} tests">
+ <option name="test-suite-tag" value="mts" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="{MODULE}" />
+ </test>
+</configuration>
diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
index 6b379e8..fb821c3 100644
--- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
+++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java
@@ -117,23 +117,32 @@
public class PermissionMonitorTest {
private static final int MOCK_USER_ID1 = 0;
private static final int MOCK_USER_ID2 = 1;
+ private static final int MOCK_USER_ID3 = 2;
private static final UserHandle MOCK_USER1 = UserHandle.of(MOCK_USER_ID1);
private static final UserHandle MOCK_USER2 = UserHandle.of(MOCK_USER_ID2);
+ private static final UserHandle MOCK_USER3 = UserHandle.of(MOCK_USER_ID3);
private static final int MOCK_APPID1 = 10001;
private static final int MOCK_APPID2 = 10086;
+ private static final int MOCK_APPID3 = 10110;
private static final int SYSTEM_APPID1 = 1100;
private static final int SYSTEM_APPID2 = 1108;
private static final int VPN_APPID = 10002;
private static final int MOCK_UID11 = MOCK_USER1.getUid(MOCK_APPID1);
private static final int MOCK_UID12 = MOCK_USER1.getUid(MOCK_APPID2);
+ private static final int MOCK_UID13 = MOCK_USER1.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID11 = MOCK_USER1.getUid(SYSTEM_APPID1);
private static final int VPN_UID = MOCK_USER1.getUid(VPN_APPID);
private static final int MOCK_UID21 = MOCK_USER2.getUid(MOCK_APPID1);
private static final int MOCK_UID22 = MOCK_USER2.getUid(MOCK_APPID2);
+ private static final int MOCK_UID23 = MOCK_USER2.getUid(MOCK_APPID3);
private static final int SYSTEM_APP_UID21 = MOCK_USER2.getUid(SYSTEM_APPID1);
+ private static final int MOCK_UID31 = MOCK_USER3.getUid(MOCK_APPID1);
+ private static final int MOCK_UID32 = MOCK_USER3.getUid(MOCK_APPID2);
+ private static final int MOCK_UID33 = MOCK_USER3.getUid(MOCK_APPID3);
private static final String REAL_SYSTEM_PACKAGE_NAME = "android";
private static final String MOCK_PACKAGE1 = "appName1";
private static final String MOCK_PACKAGE2 = "appName2";
+ private static final String MOCK_PACKAGE3 = "appName3";
private static final String SYSTEM_PACKAGE1 = "sysName1";
private static final String SYSTEM_PACKAGE2 = "sysName2";
private static final String PARTITION_SYSTEM = "system";
@@ -191,6 +200,7 @@
mBpfMapMonitor = new BpfMapMonitor(mBpfNetMaps);
doReturn(List.of()).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
}
private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion,
@@ -283,6 +293,18 @@
mPermissionMonitor.onPackageAdded(packageName, uid);
}
+ private void removePackage(String packageName, int uid) {
+ final String[] oldPackages = mPackageManager.getPackagesForUid(uid);
+ // If the package isn't existed, no need to remove it.
+ if (!CollectionUtils.contains(oldPackages, packageName)) return;
+
+ // Remove the package if this uid is shared with other packages.
+ final String[] newPackages = Arrays.stream(oldPackages).filter(e -> !e.equals(packageName))
+ .toArray(String[]::new);
+ doReturn(newPackages).when(mPackageManager).getPackagesForUid(eq(uid));
+ mPermissionMonitor.onPackageRemoved(packageName, uid);
+ }
+
@Test
public void testHasPermission() {
PackageInfo app = systemPackageInfoWithPermissions();
@@ -791,6 +813,7 @@
buildPackageInfo(SYSTEM_PACKAGE2, VPN_UID)))
.when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());
buildAndMockPackageInfoWithPermissions(MOCK_PACKAGE1, MOCK_UID11);
+ doReturn(List.of(MOCK_USER1, MOCK_USER2)).when(mUserManager).getUserHandles(eq(true));
mPermissionMonitor.startMonitoring();
final Set<UidRange> vpnRange = Set.of(UidRange.createForUser(MOCK_USER1),
@@ -881,7 +904,7 @@
addPackage(MOCK_PACKAGE1, MOCK_UID11, INTERNET, UPDATE_DEVICE_STATS);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
- // Install another package with the same uid and no permissions should not cause the app id
+ // Install another package with the same uid and no permissions should not cause the appId
// to lose permissions.
addPackage(MOCK_PACKAGE2, MOCK_UID11);
mBpfMapMonitor.expectTrafficPerm(PERMISSION_TRAFFIC_ALL, MOCK_APPID1);
@@ -1249,4 +1272,211 @@
assertTrue(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_NETWORK));
assertFalse(isHigherNetworkPermission(PERMISSION_SYSTEM, PERMISSION_SYSTEM));
}
+
+ private void prepareMultiUserPackages() {
+ // MOCK_USER1 has installed 3 packages
+ // mockApp1 has no permission and share MOCK_APPID1.
+ // mockApp2 has INTERNET permission and share MOCK_APPID2.
+ // mockApp3 has UPDATE_DEVICE_STATS permission and share MOCK_APPID3.
+ final List<PackageInfo> pkgs1 = List.of(
+ buildPackageInfo("mockApp1", MOCK_UID11),
+ buildPackageInfo("mockApp2", MOCK_UID12, INTERNET),
+ buildPackageInfo("mockApp3", MOCK_UID13, UPDATE_DEVICE_STATS));
+
+ // MOCK_USER2 has installed 2 packages
+ // mockApp4 has UPDATE_DEVICE_STATS permission and share MOCK_APPID1.
+ // mockApp5 has INTERNET permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs2 = List.of(
+ buildPackageInfo("mockApp4", MOCK_UID21, UPDATE_DEVICE_STATS),
+ buildPackageInfo("mockApp5", MOCK_UID23, INTERNET));
+
+ // MOCK_USER3 has installed 1 packages
+ // mockApp6 has UPDATE_DEVICE_STATS permission and share MOCK_APPID2.
+ final List<PackageInfo> pkgs3 = List.of(
+ buildPackageInfo("mockApp6", MOCK_UID32, UPDATE_DEVICE_STATS));
+
+ doReturn(pkgs1).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID1));
+ doReturn(pkgs2).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID2));
+ doReturn(pkgs3).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS),
+ eq(MOCK_USER_ID3));
+ }
+
+ private void addUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserAdded(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ private void removeUserAndVerifyAppIdsPermissions(UserHandle user, int appId1Perm,
+ int appId2Perm, int appId3Perm) {
+ mPermissionMonitor.onUserRemoved(user);
+ mBpfMapMonitor.expectTrafficPerm(appId1Perm, MOCK_APPID1);
+ mBpfMapMonitor.expectTrafficPerm(appId2Perm, MOCK_APPID2);
+ mBpfMapMonitor.expectTrafficPerm(appId3Perm, MOCK_APPID3);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_UserAddedRemoved() {
+ prepareMultiUserPackages();
+
+ // Add MOCK_USER1 and verify the permissions with each appIds.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Add MOCK_USER2 and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_INTERNET, PERMISSION_TRAFFIC_ALL);
+
+ // Add MOCK_USER3 and verify the permissions upgrade on MOCK_APPID2.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_TRAFFIC_ALL, PERMISSION_TRAFFIC_ALL);
+
+ // Remove MOCK_USER2 and verify the permissions downgrade on MOCK_APPID1 & MOCK_APPID3.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_NONE, PERMISSION_TRAFFIC_ALL,
+ PERMISSION_UPDATE_DEVICE_STATS);
+
+ // Remove MOCK_USER1 and verify the permissions downgrade on all appIds.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER1, PERMISSION_UNINSTALLED,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_UNINSTALLED);
+
+ // Add MOCK_USER2 back and verify the permissions upgrade on MOCK_APPID1 & MOCK_APPID3.
+ addUserAndVerifyAppIdsPermissions(MOCK_USER2, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_INTERNET);
+
+ // Remove MOCK_USER3 and verify the permissions downgrade on MOCK_APPID2.
+ removeUserAndVerifyAppIdsPermissions(MOCK_USER3, PERMISSION_UPDATE_DEVICE_STATS,
+ PERMISSION_UNINSTALLED, PERMISSION_INTERNET);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageAdded() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ final int[] netdPermissions = {PERMISSION_NONE, PERMISSION_INTERNET,
+ PERMISSION_UPDATE_DEVICE_STATS, PERMISSION_TRAFFIC_ALL};
+ final String[][] grantPermissions = {new String[]{}, new String[]{INTERNET},
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{INTERNET, UPDATE_DEVICE_STATS}};
+
+ // Verify that the permission combination is expected when same appId package is installed
+ // on another user. List the expected permissions below.
+ // NONE + NONE = NONE
+ // NONE + INTERNET = INTERNET
+ // NONE + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // NONE + ALL = ALL
+ // INTERNET + NONE = INTERNET
+ // INTERNET + INTERNET = INTERNET
+ // INTERNET + UPDATE_DEVICE_STATS = ALL
+ // INTERNET + ALL = ALL
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + INTERNET = ALL
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ // UPDATE_DEVICE_STATS + ALL = ALL
+ // ALL + NONE = ALL
+ // ALL + INTERNET = ALL
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ // ALL + ALL = ALL
+ for (int i = 0, num = 0; i < netdPermissions.length; i++) {
+ final int current = netdPermissions[i];
+ final String[] user1Perm = grantPermissions[i];
+ for (int j = 0; j < netdPermissions.length; j++) {
+ final int appId = MOCK_APPID1 + num;
+ final int added = netdPermissions[j];
+ final String[] user2Perm = grantPermissions[j];
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(current, appId);
+
+ // Add package which share the same appId on MOCK_USER2, and verify the permission
+ // has combined.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm((current | added), appId);
+ num++;
+ }
+ }
+ }
+
+ private void verifyAppIdPermissionsAfterPackageRemoved(int appId, int expectedPerm,
+ String[] user1Perm, String[] user2Perm) throws Exception {
+ // Add package on MOCK_USER1 and verify the permission is same as package granted.
+ addPackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId), user1Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Add two packages which share the same appId and don't declare permission on
+ // MOCK_USER2. Verify the permission has no change.
+ addPackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ addPackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId), user2Perm);
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove one packages from MOCK_USER2. Verify the permission has no change too.
+ removePackage(MOCK_PACKAGE2, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+
+ // Remove last packages from MOCK_USER2. Verify the permission has still no change.
+ removePackage(MOCK_PACKAGE3, MOCK_USER2.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(expectedPerm, appId);
+ }
+
+ @Test
+ public void testAppIdsTrafficPermission_Multiuser_PackageRemoved() throws Exception {
+ // Add two users with empty package list.
+ mPermissionMonitor.onUserAdded(MOCK_USER1);
+ mPermissionMonitor.onUserAdded(MOCK_USER2);
+
+ int appId = MOCK_APPID1;
+ // Verify that the permission combination is expected when same appId package is removed on
+ // another user. List the expected permissions below.
+ /***** NONE *****/
+ // NONE + NONE = NONE
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_NONE, new String[]{}, new String[]{});
+
+ /***** INTERNET *****/
+ // INTERNET + NONE = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{});
+
+ // INTERNET + INTERNET = INTERNET
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId++, PERMISSION_INTERNET, new String[]{INTERNET}, new String[]{INTERNET});
+
+ /***** UPDATE_DEVICE_STATS *****/
+ // UPDATE_DEVICE_STATS + NONE = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{});
+
+ // UPDATE_DEVICE_STATS + UPDATE_DEVICE_STATS = UPDATE_DEVICE_STATS
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_UPDATE_DEVICE_STATS,
+ new String[]{UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ /***** ALL *****/
+ // ALL + NONE = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{});
+
+ // ALL + INTERNET = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{INTERNET});
+
+ // ALL + UPDATE_DEVICE_STATS = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS}, new String[]{UPDATE_DEVICE_STATS});
+
+ // ALL + ALL = ALL
+ verifyAppIdPermissionsAfterPackageRemoved(appId++, PERMISSION_TRAFFIC_ALL,
+ new String[]{INTERNET, UPDATE_DEVICE_STATS},
+ new String[]{INTERNET, UPDATE_DEVICE_STATS});
+
+ /***** UNINSTALL *****/
+ // UNINSTALL + UNINSTALL = UNINSTALL
+ verifyAppIdPermissionsAfterPackageRemoved(
+ appId, PERMISSION_NONE, new String[]{}, new String[]{});
+ removePackage(MOCK_PACKAGE1, MOCK_USER1.getUid(appId));
+ mBpfMapMonitor.expectTrafficPerm(PERMISSION_UNINSTALLED, appId);
+ }
}