Loading services/core/java/com/android/server/ConnectivityService.java +36 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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++) { Loading @@ -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++) { Loading @@ -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. Loading @@ -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)) { Loading @@ -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); } } }; Loading services/core/java/com/android/server/connectivity/PermissionMonitor.java +47 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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<Integer> mUsers = new HashSet<>(); Loading @@ -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 Loading @@ -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<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS); if (apps == null) { loge("No apps"); Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; } Loading @@ -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<Integer, Boolean> 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<Integer, Boolean> 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); Loading @@ -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); } } Loading tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +174 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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"; Loading @@ -63,6 +80,7 @@ public class PermissionMonitorTest { @Mock private Context mContext; @Mock private PackageManager mPackageManager; @Mock private INetworkManagementService mNMS; private PermissionMonitor mPermissionMonitor; Loading @@ -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, Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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_UID1)); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); } private class NMSMonitor { private final HashMap<Integer, Boolean> 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}); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID)); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); // 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)); } } } Loading
services/core/java/com/android/server/ConnectivityService.java +36 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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++) { Loading @@ -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++) { Loading @@ -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. Loading @@ -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)) { Loading @@ -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); } } }; Loading
services/core/java/com/android/server/connectivity/PermissionMonitor.java +47 −63 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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<Integer> mUsers = new HashSet<>(); Loading @@ -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 Loading @@ -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<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS); if (apps == null) { loge("No apps"); Loading Loading @@ -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; Loading @@ -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; Loading @@ -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; } Loading @@ -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<Integer, Boolean> 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<Integer, Boolean> 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); Loading @@ -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); } } Loading
tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +174 −21 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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"; Loading @@ -63,6 +80,7 @@ public class PermissionMonitorTest { @Mock private Context mContext; @Mock private PackageManager mPackageManager; @Mock private INetworkManagementService mNMS; private PermissionMonitor mPermissionMonitor; Loading @@ -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, Loading @@ -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); } Loading Loading @@ -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 Loading @@ -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_UID1)); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID1, CHANGE_WIFI_STATE)); } private class NMSMonitor { private final HashMap<Integer, Boolean> 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}); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID)); assertFalse(hasBgPermission(PARTITION_VENDOR, VERSION_Q, MOCK_UID, CHANGE_WIFI_STATE)); // 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)); } } }