From df46b3527911f2bca1654ae0e6b7697df1cd654f Mon Sep 17 00:00:00 2001 From: junyulai Date: Thu, 1 Nov 2018 17:16:31 +0800 Subject: [PATCH] PermissionMonitor: Move intent receiver to ConnectivityService. Currently, PermissionMonitor listen to user add/remove and package add/remove intent respectively, and so does VPN. Thus, races might occurr between them. This commit refactor PermissionMonitor part by using ConnectivityService to listen to intents and dispatch events to PermissionMonitor. Bug: 118811303 Test: 1. atest FrameworksNetTests 2. manually add/remove package Change-Id: I6e45b5870d5b1300cad252d25bdb4da78f9bf70e --- .../android/server/ConnectivityService.java | 38 +++- .../connectivity/PermissionMonitor.java | 110 +++++----- .../connectivity/PermissionMonitorTest.java | 195 ++++++++++++++++-- 3 files changed, 257 insertions(+), 86 deletions(-) diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 74c80237d7..564d35a093 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -894,10 +894,18 @@ public class ConnectivityService extends IConnectivityManager.Stub intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mContext.registerReceiverAsUser( - mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); + mIntentReceiver, UserHandle.ALL, intentFilter, null, null); mContext.registerReceiverAsUser(mUserPresentReceiver, UserHandle.SYSTEM, new IntentFilter(Intent.ACTION_USER_PRESENT), null, null); + // Listen to package add and removal events for all users. + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser( + mIntentReceiver, UserHandle.ALL, intentFilter, null, null); + try { mNMS.registerObserver(mTethering); mNMS.registerObserver(mDataActivityObserver); @@ -4155,6 +4163,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void onUserAdded(int userId) { + mPermissionMonitor.onUserAdded(userId); synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { @@ -4165,6 +4174,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void onUserRemoved(int userId) { + mPermissionMonitor.onUserRemoved(userId); synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { @@ -4174,6 +4184,22 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private void onPackageAdded(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Slog.wtf(TAG, "Invalid package in onPackageAdded: " + packageName + " | " + uid); + return; + } + mPermissionMonitor.onPackageAdded(packageName, uid); + } + + private void onPackageRemoved(String packageName, int uid) { + if (TextUtils.isEmpty(packageName) || uid < 0) { + Slog.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); + return; + } + mPermissionMonitor.onPackageRemoved(uid); + } + private void onUserUnlocked(int userId) { synchronized (mVpns) { // User present may be sent because of an unlock, which might mean an unlocked keystore. @@ -4185,11 +4211,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + final Uri packageData = intent.getData(); + final String packageName = + packageData != null ? packageData.getSchemeSpecificPart() : null; if (userId == UserHandle.USER_NULL) return; if (Intent.ACTION_USER_STARTED.equals(action)) { @@ -4202,6 +4232,10 @@ public class ConnectivityService extends IConnectivityManager.Stub onUserRemoved(userId); } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { onUserUnlocked(userId); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + onPackageAdded(packageName, uid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + onPackageRemoved(packageName, uid); } } }; diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 94c94a514d..420b23e6a3 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -27,10 +27,7 @@ import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import android.annotation.NonNull; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -42,7 +39,6 @@ import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; -import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -64,15 +60,14 @@ import java.util.Set; public class PermissionMonitor { private static final String TAG = "PermissionMonitor"; private static final boolean DBG = true; - private static final Boolean SYSTEM = Boolean.TRUE; - private static final Boolean NETWORK = Boolean.FALSE; + protected static final Boolean SYSTEM = Boolean.TRUE; + protected static final Boolean NETWORK = Boolean.FALSE; private static final int VERSION_Q = Build.VERSION_CODES.Q; private final Context mContext; private final PackageManager mPackageManager; private final UserManager mUserManager; private final INetworkManagementService mNetd; - private final BroadcastReceiver mIntentReceiver; // Values are User IDs. private final Set mUsers = new HashSet<>(); @@ -85,26 +80,6 @@ public class PermissionMonitor { mPackageManager = context.getPackageManager(); mUserManager = UserManager.get(context); mNetd = netd; - mIntentReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); - int appUid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID); - Uri appData = intent.getData(); - String appName = appData != null ? appData.getSchemeSpecificPart() : null; - - if (Intent.ACTION_USER_ADDED.equals(action)) { - onUserAdded(user); - } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(user); - } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { - onAppAdded(appName, appUid); - } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { - onAppRemoved(appUid); - } - } - }; } // Intended to be called only once at startup, after the system is ready. Installs a broadcast @@ -112,17 +87,6 @@ public class PermissionMonitor { public synchronized void startMonitoring() { log("Monitoring"); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_USER_ADDED); - intentFilter.addAction(Intent.ACTION_USER_REMOVED); - mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null); - - intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - intentFilter.addDataScheme("package"); - mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null); - List apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS); if (apps == null) { loge("No apps"); @@ -260,7 +224,14 @@ public class PermissionMonitor { } } - private synchronized void onUserAdded(int user) { + /** + * Called when a user is added. See {link #ACTION_USER_ADDED}. + * + * @param user The integer userHandle of the added user. See {@link #EXTRA_USER_HANDLE}. + * + * @hide + */ + public synchronized void onUserAdded(int user) { if (user < 0) { loge("Invalid user in onUserAdded: " + user); return; @@ -272,7 +243,14 @@ public class PermissionMonitor { update(users, mApps, true); } - private synchronized void onUserRemoved(int user) { + /** + * Called when an user is removed. See {link #ACTION_USER_REMOVED}. + * + * @param user The integer userHandle of the removed user. See {@link #EXTRA_USER_HANDLE}. + * + * @hide + */ + public synchronized void onUserRemoved(int user) { if (user < 0) { loge("Invalid user in onUserRemoved: " + user); return; @@ -284,8 +262,8 @@ public class PermissionMonitor { update(users, mApps, false); } - - private Boolean highestPermissionForUid(Boolean currentPermission, String name) { + @VisibleForTesting + protected Boolean highestPermissionForUid(Boolean currentPermission, String name) { if (currentPermission == SYSTEM) { return currentPermission; } @@ -303,33 +281,39 @@ public class PermissionMonitor { return currentPermission; } - private synchronized void onAppAdded(String appName, int appUid) { - if (TextUtils.isEmpty(appName) || appUid < 0) { - loge("Invalid app in onAppAdded: " + appName + " | " + appUid); - return; - } - + /** + * Called when a package is added. See {link #ACTION_PACKAGE_ADDED}. + * + * @param packageName The name of the new package. + * @param uid The uid of the new package. + * + * @hide + */ + public synchronized void onPackageAdded(String packageName, int uid) { // If multiple packages share a UID (cf: android:sharedUserId) and ask for different // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is). - final Boolean permission = highestPermissionForUid(mApps.get(appUid), appName); - if (permission != mApps.get(appUid)) { - mApps.put(appUid, permission); + final Boolean permission = highestPermissionForUid(mApps.get(uid), packageName); + if (permission != mApps.get(uid)) { + mApps.put(uid, permission); Map apps = new HashMap<>(); - apps.put(appUid, permission); + apps.put(uid, permission); update(mUsers, apps, true); } } - private synchronized void onAppRemoved(int appUid) { - if (appUid < 0) { - loge("Invalid app in onAppRemoved: " + appUid); - return; - } + /** + * Called when a package is removed. See {link #ACTION_PACKAGE_REMOVED}. + * + * @param uid containing the integer uid previously assigned to the package. + * + * @hide + */ + public synchronized void onPackageRemoved(int uid) { Map apps = new HashMap<>(); Boolean permission = null; - String[] packages = mPackageManager.getPackagesForUid(appUid); + String[] packages = mPackageManager.getPackagesForUid(uid); if (packages != null && packages.length > 0) { for (String name : packages) { permission = highestPermissionForUid(permission, name); @@ -341,16 +325,16 @@ public class PermissionMonitor { } } } - if (permission == mApps.get(appUid)) { + if (permission == mApps.get(uid)) { // The permissions of this UID have not changed. Nothing to do. return; } else if (permission != null) { - mApps.put(appUid, permission); - apps.put(appUid, permission); + mApps.put(uid, permission); + apps.put(uid, permission); update(mUsers, apps, true); } else { - mApps.remove(appUid); - apps.put(appUid, NETWORK); // doesn't matter which permission we pick here + mApps.remove(uid); + apps.put(uid, NETWORK); // doesn't matter which permission we pick here update(mUsers, apps, false); } } diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index af7123b848..f2bd770d08 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -27,9 +27,17 @@ import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_VENDOR; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.os.Process.SYSTEM_UID; +import static com.android.server.connectivity.PermissionMonitor.NETWORK; +import static com.android.server.connectivity.PermissionMonitor.SYSTEM; + +import static junit.framework.Assert.fail; + import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.spy; @@ -40,6 +48,8 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; +import android.os.INetworkManagementService; +import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -48,12 +58,19 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; + +import java.util.HashMap; @RunWith(AndroidJUnit4.class) @SmallTest public class PermissionMonitorTest { - private static final int MOCK_UID = 10001; - private static final String[] MOCK_PACKAGE_NAMES = new String[] { "com.foo.bar" }; + private static final int MOCK_USER1 = 0; + private static final int MOCK_USER2 = 1; + private static final int MOCK_UID1 = 10001; + private static final String MOCK_PACKAGE1 = "appName1"; + private static final String SYSTEM_PACKAGE1 = "sysName1"; + private static final String SYSTEM_PACKAGE2 = "sysName2"; private static final String PARTITION_SYSTEM = "system"; private static final String PARTITION_OEM = "oem"; private static final String PARTITION_PRODUCT = "product"; @@ -63,6 +80,7 @@ public class PermissionMonitorTest { @Mock private Context mContext; @Mock private PackageManager mPackageManager; + @Mock private INetworkManagementService mNMS; private PermissionMonitor mPermissionMonitor; @@ -70,8 +88,7 @@ public class PermissionMonitorTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(mContext.getPackageManager()).thenReturn(mPackageManager); - when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(MOCK_PACKAGE_NAMES); - mPermissionMonitor = spy(new PermissionMonitor(mContext, null)); + mPermissionMonitor = spy(new PermissionMonitor(mContext, mNMS)); } private boolean hasBgPermission(String partition, int targetSdkVersion, int uid, @@ -80,7 +97,8 @@ public class PermissionMonitorTest { packageInfo.applicationInfo.targetSdkVersion = targetSdkVersion; packageInfo.applicationInfo.uid = uid; when(mPackageManager.getPackageInfoAsUser( - eq(MOCK_PACKAGE_NAMES[0]), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo); + eq(MOCK_PACKAGE1), eq(GET_PERMISSIONS), anyInt())).thenReturn(packageInfo); + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[] {MOCK_PACKAGE1}); return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid); } @@ -143,16 +161,16 @@ public class PermissionMonitorTest { @Test public void testHasUseBackgroundNetworksPermission() throws Exception { - assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID)); - assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CHANGE_NETWORK_STATE)); - assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, NETWORK_STACK)); - assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CONNECTIVITY_INTERNAL)); - assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, NETWORK_STACK)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL)); + assertTrue(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); - assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID, CHANGE_WIFI_STATE)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE)); - assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID)); - assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1)); + assertFalse(hasBgPermission(PARTITION_SYSTEM, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); } @Test @@ -172,15 +190,150 @@ public class PermissionMonitorTest { @Test public void testHasUseBackgroundNetworksPermissionVendorApp() throws Exception { - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID)); - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CHANGE_NETWORK_STATE)); - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, NETWORK_STACK)); - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CONNECTIVITY_INTERNAL)); - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_NETWORK_STATE)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, NETWORK_STACK)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_INTERNAL)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); - assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID, CHANGE_WIFI_STATE)); + assertTrue(hasBgPermission(PARTITION_VENDOR, VERSION_P, MOCK_UID1, CHANGE_WIFI_STATE)); - assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID)); - assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); + assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1)); + assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); + } + + private class NMSMonitor { + private final HashMap mApps = new HashMap<>(); + + NMSMonitor(INetworkManagementService mockNMS) throws Exception { + // Add hook to verify and track result of setPermission. + doAnswer((InvocationOnMock invocation) -> { + final Object[] args = invocation.getArguments(); + final Boolean isSystem = args[0].equals("SYSTEM"); + for (final int uid : (int[]) args[1]) { + // TODO: Currently, permission monitor will send duplicate commands for each uid + // corresponding to each user. Need to fix that and uncomment below test. + // if (mApps.containsKey(uid) && mApps.get(uid) == isSystem) { + // fail("uid " + uid + " is already set to " + isSystem); + // } + mApps.put(uid, isSystem); + } + return null; + }).when(mockNMS).setPermission(anyString(), any(int[].class)); + + // Add hook to verify and track result of clearPermission. + doAnswer((InvocationOnMock invocation) -> { + final Object[] args = invocation.getArguments(); + for (final int uid : (int[]) args[0]) { + // TODO: Currently, permission monitor will send duplicate commands for each uid + // corresponding to each user. Need to fix that and uncomment below test. + // if (!mApps.containsKey(uid)) { + // fail("uid " + uid + " does not exist."); + // } + mApps.remove(uid); + } + return null; + }).when(mockNMS).clearPermission(any(int[].class)); + } + + public void expectPermission(Boolean permission, int[] users, int[] apps) { + for (final int user : users) { + for (final int app : apps) { + final int uid = UserHandle.getUid(user, app); + if (!mApps.containsKey(uid)) { + fail("uid " + uid + " does not exist."); + } + if (mApps.get(uid) != permission) { + fail("uid " + uid + " has wrong permission: " + permission); + } + } + } + } + + public void expectNoPermission(int[] users, int[] apps) { + for (final int user : users) { + for (final int app : apps) { + final int uid = UserHandle.getUid(user, app); + if (mApps.containsKey(uid)) { + fail("uid " + uid + " has listed permissions, expected none."); + } + } + } + } + } + + @Test + public void testUserAndPackageAddRemove() throws Exception { + final NMSMonitor mNMSMonitor = new NMSMonitor(mNMS); + + // MOCK_UID1: MOCK_PACKAGE1 only has network permission. + // SYSTEM_UID: SYSTEM_PACKAGE1 has system permission. + // SYSTEM_UID: SYSTEM_PACKAGE2 only has network permission. + doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(eq(SYSTEM), anyString()); + doReturn(SYSTEM).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(SYSTEM_PACKAGE1)); + doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(SYSTEM_PACKAGE2)); + doReturn(NETWORK).when(mPermissionMonitor).highestPermissionForUid(any(), + eq(MOCK_PACKAGE1)); + + // Add SYSTEM_PACKAGE2, expect only have network permission. + mPermissionMonitor.onUserAdded(MOCK_USER1); + addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + + // Add SYSTEM_PACKAGE1, expect permission escalate. + addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); + mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + + mPermissionMonitor.onUserAdded(MOCK_USER2); + mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + + addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNMSMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); + + // Remove MOCK_UID1, expect no permission left for all user. + mPermissionMonitor.onPackageRemoved(MOCK_UID1); + removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_UID1); + mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); + + // Remove SYSTEM_PACKAGE1, expect permission downgrade. + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2}); + removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_UID); + mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID}); + + mPermissionMonitor.onUserRemoved(MOCK_USER1); + mNMSMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID}); + + // Remove all packages, expect no permission left. + when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{}); + removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_UID); + mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID, MOCK_UID1}); + + // Remove last user, expect no redundant clearPermission is invoked. + mPermissionMonitor.onUserRemoved(MOCK_USER2); + mNMSMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + new int[]{SYSTEM_UID, MOCK_UID1}); + } + + // Normal package add/remove operations will trigger multiple intent for uids corresponding to + // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be + // called multiple times with the uid corresponding to each user. + private void addPackageForUsers(int[] users, String packageName, int uid) { + for (final int user : users) { + mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid)); + } + } + + private void removePackageForUsers(int[] users, int uid) { + for (final int user : users) { + mPermissionMonitor.onPackageRemoved(UserHandle.getUid(user, uid)); + } } }