diff options
10 files changed, 406 insertions, 9 deletions
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java index 93e14dec2856..5cd0981f72e3 100644 --- a/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.res.Configuration; import java.util.List; +import java.util.Set; /** * UsageStatsManager local system service interface. @@ -159,6 +160,22 @@ public abstract class UsageStatsManagerInternal { public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload); /** + * Called by DevicePolicyManagerService to inform that a new admin has been added. + * + * @param packageName the package in which the admin component is part of. + * @param userId the userId in which the admin has been added. + */ + public abstract void onActiveAdminAdded(String packageName, int userId); + + /** + * Called by DevicePolicyManagerService to inform about the active admins in an user. + * + * @param adminApps the set of active admins in {@param userId} or null if there are none. + * @param userId the userId to which the admin apps belong. + */ + public abstract void setActiveAdminApps(Set<String> adminApps, int userId); + + /** * Return usage stats. * * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java index c1e95ebeddf2..7d64e4342620 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java @@ -38,6 +38,12 @@ import java.util.List; */ abstract class BaseIDevicePolicyManager extends IDevicePolicyManager.Stub { /** + * To be called by {@link DevicePolicyManagerService#Lifecycle} when the service is started. + * + * @see {@link SystemService#onStart}. + */ + abstract void handleStart(); + /** * To be called by {@link DevicePolicyManagerService#Lifecycle} during the various boot phases. * * @see {@link SystemService#onBootPhase}. diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f0681e9eb32e..3c6187ba4aeb 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -95,6 +95,7 @@ import android.app.admin.SystemUpdateInfo; import android.app.admin.SystemUpdatePolicy; import android.app.backup.IBackupManager; import android.app.trust.TrustManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -401,6 +402,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { final IPackageManager mIPackageManager; final UserManager mUserManager; final UserManagerInternal mUserManagerInternal; + final UsageStatsManagerInternal mUsageStatsManagerInternal; final TelephonyManager mTelephonyManager; private final LockPatternUtils mLockPatternUtils; private final DevicePolicyConstants mConstants; @@ -504,6 +506,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { @Override public void onStart() { publishBinderService(Context.DEVICE_POLICY_SERVICE, mService); + mService.handleStart(); } @Override @@ -1560,6 +1563,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { removedAdmin = true; policy.mAdminList.remove(i); policy.mAdminMap.remove(aa.info.getComponent()); + pushActiveAdminPackagesLocked(userHandle); } } } catch (RemoteException re) { @@ -1653,6 +1657,10 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return LocalServices.getService(PackageManagerInternal.class); } + UsageStatsManagerInternal getUsageStatsManagerInternal() { + return LocalServices.getService(UsageStatsManagerInternal.class); + } + NotificationManager getNotificationManager() { return mContext.getSystemService(NotificationManager.class); } @@ -1923,6 +1931,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { mUserManager = Preconditions.checkNotNull(injector.getUserManager()); mUserManagerInternal = Preconditions.checkNotNull(injector.getUserManagerInternal()); + mUsageStatsManagerInternal = Preconditions.checkNotNull( + injector.getUsageStatsManagerInternal()); mIPackageManager = Preconditions.checkNotNull(injector.getIPackageManager()); mTelephonyManager = Preconditions.checkNotNull(injector.getTelephonyManager()); @@ -3190,6 +3200,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } @Override + void handleStart() { + pushActiveAdminPackages(); + } + + @Override void handleStartUser(int userId) { updateScreenCaptureDisabledInWindowManager(userId, getScreenCaptureDisabled(null, userId)); @@ -3357,6 +3372,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (replaceIndex == -1) { policy.mAdminList.add(newAdmin); enableIfNecessary(info.getPackageName(), userHandle); + mUsageStatsManagerInternal.onActiveAdminAdded( + adminReceiver.getPackageName(), userHandle); } else { policy.mAdminList.set(replaceIndex, newAdmin); } @@ -3369,6 +3386,35 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private void pushActiveAdminPackages() { + synchronized (this) { + final List<UserInfo> users = mUserManager.getUsers(); + for (int i = users.size() - 1; i >= 0; --i) { + final int userId = users.get(i).id; + mUsageStatsManagerInternal.setActiveAdminApps( + getActiveAdminPackagesLocked(userId), userId); + } + } + } + + private void pushActiveAdminPackagesLocked(int userId) { + mUsageStatsManagerInternal.setActiveAdminApps( + getActiveAdminPackagesLocked(userId), userId); + } + + private Set<String> getActiveAdminPackagesLocked(int userId) { + final DevicePolicyData policy = getUserData(userId); + Set<String> adminPkgs = null; + for (int i = policy.mAdminList.size() - 1; i >= 0; --i) { + final String pkgName = policy.mAdminList.get(i).info.getPackageName(); + if (adminPkgs == null) { + adminPkgs = new ArraySet<>(); + } + adminPkgs.add(pkgName); + } + return adminPkgs; + } + private void transferActiveAdminUncheckedLocked(ComponentName incomingReceiver, ComponentName outgoingReceiver, int userHandle) { final DevicePolicyData policy = getUserData(userHandle); @@ -3487,6 +3533,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + @Override public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) { if (!mHasFeature) { return; @@ -3540,7 +3587,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private boolean isPackageTestOnly(String packageName, int userHandle) { final ApplicationInfo ai; try { - ai = mIPackageManager.getApplicationInfo(packageName, + ai = mInjector.getIPackageManager().getApplicationInfo(packageName, (PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE), userHandle); } catch (RemoteException e) { @@ -3562,7 +3609,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } private void enforceShell(String method) { - final int callingUid = Binder.getCallingUid(); + final int callingUid = mInjector.binderGetCallingUid(); if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) { throw new SecurityException("Non-shell user attempted to call " + method); } @@ -11140,6 +11187,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { if (doProxyCleanup) { resetGlobalProxyLocked(policy); } + pushActiveAdminPackagesLocked(userHandle); saveSettingsLocked(userHandle); updateMaximumTimeToLockLocked(userHandle); policy.mRemovingAdmins.remove(adminReceiver); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java index 0650acb45c47..f5894e07303c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java @@ -20,6 +20,7 @@ import android.app.IActivityManager; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.backup.IBackupManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; @@ -152,6 +153,11 @@ public class DevicePolicyManagerServiceTestable extends DevicePolicyManagerServi } @Override + UsageStatsManagerInternal getUsageStatsManagerInternal() { + return services.usageStatsManagerInternal; + } + + @Override PackageManagerInternal getPackageManagerInternal() { return services.packageManagerInternal; } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 58ac7d28eade..57030e433fa9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -44,6 +44,7 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import static org.mockito.hamcrest.MockitoHamcrest.argThat; @@ -90,6 +91,7 @@ import com.android.server.pm.UserRestrictionsUtils; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; +import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -182,6 +184,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { initializeDpms(); + Mockito.reset(getServices().usageStatsManagerInternal); setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID); setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID); setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID); @@ -207,6 +210,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext); + dpms.handleStart(); dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY); dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED); @@ -278,6 +282,32 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertNull(LocalServices.getService(DevicePolicyManagerInternal.class)); } + public void testHandleStart() throws Exception { + // Device owner in SYSTEM_USER + setDeviceOwner(); + // Profile owner in CALLER_USER_HANDLE + setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID); + setAsProfileOwner(admin2); + // Active admin in CALLER_USER_HANDLE + final int ANOTHER_UID = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, 1306); + setUpPackageManagerForFakeAdmin(adminAnotherPackage, ANOTHER_UID, admin2); + dpm.setActiveAdmin(adminAnotherPackage, /* replace =*/ false, + DpmMockContext.CALLER_USER_HANDLE); + assertTrue(dpm.isAdminActiveAsUser(adminAnotherPackage, + DpmMockContext.CALLER_USER_HANDLE)); + + initializeDpms(); + + // Verify + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + MockUtils.checkAdminApps(admin1.getPackageName()), + eq(UserHandle.USER_SYSTEM)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + MockUtils.checkAdminApps(admin2.getPackageName(), + adminAnotherPackage.getPackageName()), + eq(DpmMockContext.CALLER_USER_HANDLE)); + } + /** * Caller doesn't have proper permissions. */ @@ -330,6 +360,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(DpmMockContext.CALLER_USER_HANDLE), anyString()); + verify(getServices().usageStatsManagerInternal).onActiveAdminAdded( + admin1.getPackageName(), DpmMockContext.CALLER_USER_HANDLE); + // TODO Verify other calls too. // Make sure it's active admin1. @@ -369,6 +402,11 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(DpmMockContext.CALLER_USER_HANDLE), anyString()); + // times(2) because it was previously called for admin1 which is in the same package + // as admin2. + verify(getServices().usageStatsManagerInternal, times(2)).onActiveAdminAdded( + admin2.getPackageName(), DpmMockContext.CALLER_USER_HANDLE); + // 4. Add the same admin1 again without replace, which should throw. assertExpectException(IllegalArgumentException.class, /* messageRegex= */ null, () -> dpm.setActiveAdmin(admin1, /* replace =*/ false)); @@ -384,6 +422,10 @@ public class DevicePolicyManagerTest extends DpmTestBase { assertEquals(admin1, admins.get(0)); assertEquals(admin2, admins.get(1)); + // There shouldn't be any callback to UsageStatsManagerInternal when the admin is being + // replaced + verifyNoMoreInteractions(getServices().usageStatsManagerInternal); + // Another user has no admins. mContext.callerPermissions.add("android.permission.INTERACT_ACROSS_USERS_FULL"); @@ -516,6 +558,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { () -> dpm.removeActiveAdmin(admin1)); assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal, times(0)).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); // 2. User unlocked. when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE))) @@ -523,6 +567,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpm.removeActiveAdmin(admin1); assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); } /** @@ -547,6 +593,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { dpms.removeActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE); assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); // TODO DO Still can't be removed in this case. } @@ -588,6 +636,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { isNull(Bundle.class)); assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); // Again broadcast from saveSettingsLocked(). verify(mContext.spiedContext, times(2)).sendBroadcastAsUser( @@ -598,6 +648,86 @@ public class DevicePolicyManagerTest extends DpmTestBase { // TODO Check other internal calls. } + public void testRemoveActiveAdmin_multipleAdminsInUser() { + // Need MANAGE_DEVICE_ADMINS for setActiveAdmin. We'll remove it later. + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin1. + dpm.setActiveAdmin(admin1, /* replace =*/ false); + + assertTrue(dpm.isAdminActive(admin1)); + assertFalse(dpm.isRemovingAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE)); + + // Add admin2. + dpm.setActiveAdmin(admin2, /* replace =*/ false); + + assertTrue(dpm.isAdminActive(admin2)); + assertFalse(dpm.isRemovingAdmin(admin2, DpmMockContext.CALLER_USER_HANDLE)); + + // Broadcast from saveSettingsLocked(). + verify(mContext.spiedContext, times(2)).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + + // Remove. No permissions, but same user, so it'll work. + mContext.callerPermissions.clear(); + dpm.removeActiveAdmin(admin1); + + verify(mContext.spiedContext).sendOrderedBroadcastAsUser( + MockUtils.checkIntentAction( + DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE), + isNull(String.class), + any(BroadcastReceiver.class), + eq(dpms.mHandler), + eq(Activity.RESULT_OK), + isNull(String.class), + isNull(Bundle.class)); + + assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + MockUtils.checkAdminApps(admin2.getPackageName()), + eq(DpmMockContext.CALLER_USER_HANDLE)); + + // Again broadcast from saveSettingsLocked(). + verify(mContext.spiedContext, times(3)).sendBroadcastAsUser( + MockUtils.checkIntentAction( + DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED), + MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE)); + } + + /** + * Test for: + * {@link DevicePolicyManager#forceRemoveActiveAdmin(ComponentName, int)} + */ + public void testForceRemoveActiveAdmin() throws Exception { + mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS); + + // Add admin. + setupPackageInPackageManager(admin1.getPackageName(), + /* userId= */ DpmMockContext.CALLER_USER_HANDLE, + /* appId= */ 10138, + /* flags= */ ApplicationInfo.FLAG_TEST_ONLY); + dpm.setActiveAdmin(admin1, /* replace =*/ false); + assertTrue(dpm.isAdminActive(admin1)); + + // Calling from a non-shell uid should fail with a SecurityException + mContext.binder.callingUid = 123456; + assertExpectException(SecurityException.class, + /* messageRegex =*/ "Non-shell user attempted to call", + () -> dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE)); + + mContext.binder.callingUid = Process.SHELL_UID; + dpms.forceRemoveActiveAdmin(admin1, DpmMockContext.CALLER_USER_HANDLE); + + mContext.callerPermissions.add(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL); + // Verify + assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); + } + /** * Test for: @{link DevicePolicyManager#setActivePasswordState} * @@ -954,6 +1084,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { eq(null), eq(true), eq(CAMERA_NOT_DISABLED)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, UserHandle.USER_SYSTEM); + assertFalse(dpm.isAdminActiveAsUser(admin1, UserHandle.USER_SYSTEM)); // ACTION_DEVICE_OWNER_CHANGED should be sent twice, once for setting the device owner @@ -1044,6 +1177,8 @@ public class DevicePolicyManagerTest extends DpmTestBase { // Check assertFalse(dpm.isProfileOwnerApp(admin1.getPackageName())); assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE)); + verify(getServices().usageStatsManagerInternal).setActiveAdminApps( + null, DpmMockContext.CALLER_USER_HANDLE); } public void testSetProfileOwner_failures() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java index 4232c4405983..11e32f8326d9 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java @@ -29,6 +29,7 @@ import android.app.AlarmManager; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; +import android.app.usage.UsageStatsManagerInternal; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -73,6 +74,7 @@ public class MockSystemServices { public final SystemPropertiesForMock systemProperties; public final UserManager userManager; public final UserManagerInternal userManagerInternal; + public final UsageStatsManagerInternal usageStatsManagerInternal; public final PackageManagerInternal packageManagerInternal; public final UserManagerForMock userManagerForMock; public final PowerManagerForMock powerManager; @@ -108,6 +110,7 @@ public class MockSystemServices { systemProperties = mock(SystemPropertiesForMock.class); userManager = mock(UserManager.class); userManagerInternal = mock(UserManagerInternal.class); + usageStatsManagerInternal = mock(UsageStatsManagerInternal.class); userManagerForMock = mock(UserManagerForMock.class); packageManagerInternal = mock(PackageManagerInternal.class); powerManager = mock(PowerManagerForMock.class); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java index e43786c43cc3..288a8bedf713 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java @@ -24,13 +24,16 @@ import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.os.UserHandle; +import android.util.ArraySet; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; -import org.mockito.Mockito; import org.mockito.hamcrest.MockitoHamcrest; +import java.util.Arrays; +import java.util.Set; + public class MockUtils { private MockUtils() { } @@ -115,6 +118,30 @@ public class MockUtils { return MockitoHamcrest.argThat(m); } + public static Set<String> checkAdminApps(String... adminApps) { + final Matcher<Set<String>> m = new BaseMatcher<Set<String>>() { + @Override + public boolean matches(Object item) { + if (item == null) return false; + final Set<String> actualAdminApps = (Set<String>) item; + if (adminApps.length != actualAdminApps.size()) { + return false; + } + final Set<String> copyOfAdmins = new ArraySet<>(actualAdminApps); + for (String adminApp : adminApps) { + copyOfAdmins.remove(adminApp); + } + return copyOfAdmins.isEmpty(); + } + + @Override + public void describeTo(Description description) { + description.appendText("Admin apps=" + Arrays.toString(adminApps)); + } + }; + return MockitoHamcrest.argThat(m); + } + private static String getRestrictionsAsString(Bundle b) { final StringBuilder sb = new StringBuilder(); sb.append("["); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 725fb210ba22..40964c03db81 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -34,13 +34,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import android.app.usage.UsageEvents; -import android.app.usage.UsageStatsManager; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.ContextWrapper; @@ -55,6 +55,7 @@ import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; +import android.util.ArraySet; import android.view.Display; import com.android.server.SystemService; @@ -65,7 +66,9 @@ import org.junit.runner.RunWith; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Set; /** * Unit test for AppStandbyController. @@ -78,6 +81,11 @@ public class AppStandbyControllerTests { private static final String PACKAGE_1 = "com.example.foo"; private static final int UID_1 = 10000; private static final int USER_ID = 0; + private static final int USER_ID2 = 10; + + private static final String ADMIN_PKG = "com.android.admin"; + private static final String ADMIN_PKG2 = "com.android.admin2"; + private static final String ADMIN_PKG3 = "com.android.admin3"; private static final long MINUTE_MS = 60 * 1000; private static final long HOUR_MS = 60 * MINUTE_MS; @@ -454,6 +462,105 @@ public class AppStandbyControllerTests { mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT, REASON_PREDICTED, mInjector.mElapsedRealtime); assertBucket(STANDBY_BUCKET_FREQUENT); + } + + @Test + public void testAddActiveDeviceAdmin() { + assertActiveAdmins(USER_ID, (String[]) null); + assertActiveAdmins(USER_ID2, (String[]) null); + + mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); + assertActiveAdmins(USER_ID, ADMIN_PKG); + assertActiveAdmins(USER_ID2, (String[]) null); + + mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); + assertActiveAdmins(USER_ID, ADMIN_PKG); + assertActiveAdmins(USER_ID2, (String[]) null); + + mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); + assertActiveAdmins(USER_ID, ADMIN_PKG); + assertActiveAdmins(USER_ID2, ADMIN_PKG2); + } + + @Test + public void testSetActiveAdminApps() { + assertActiveAdmins(USER_ID, (String[]) null); + assertActiveAdmins(USER_ID2, (String[]) null); + + setActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); + assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); + assertActiveAdmins(USER_ID2, (String[]) null); + + mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); + setActiveAdmins(USER_ID2, ADMIN_PKG); + assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2); + assertActiveAdmins(USER_ID2, ADMIN_PKG); + + mController.setActiveAdminApps(null, USER_ID); + assertActiveAdmins(USER_ID, (String[]) null); + } + + @Test + public void isActiveDeviceAdmin() { + assertActiveAdmins(USER_ID, (String[]) null); + assertActiveAdmins(USER_ID2, (String[]) null); + + mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID); + assertIsActiveAdmin(ADMIN_PKG, USER_ID); + assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2); + + mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2); + mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID2); + assertIsActiveAdmin(ADMIN_PKG, USER_ID); + assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); + assertIsActiveAdmin(ADMIN_PKG, USER_ID2); + assertIsActiveAdmin(ADMIN_PKG2, USER_ID2); + + setActiveAdmins(USER_ID2, ADMIN_PKG2); + assertIsActiveAdmin(ADMIN_PKG2, USER_ID2); + assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2); + assertIsActiveAdmin(ADMIN_PKG, USER_ID); + assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID); + } + + private String getAdminAppsStr(int userId) { + return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId)); + } + + private String getAdminAppsStr(int userId, Set<String> adminApps) { + return "admin apps for u" + userId + ": " + + (adminApps == null ? "null" : Arrays.toString(adminApps.toArray())); + } + + private void assertIsActiveAdmin(String adminApp, int userId) { + assertTrue(adminApp + " should be an active admin; " + getAdminAppsStr(userId), + mController.isActiveDeviceAdmin(adminApp, userId)); + } + + private void assertIsNotActiveAdmin(String adminApp, int userId) { + assertFalse(adminApp + " shouldn't be an active admin; " + getAdminAppsStr(userId), + mController.isActiveDeviceAdmin(adminApp, userId)); + } + + private void assertActiveAdmins(int userId, String... admins) { + final Set<String> actualAdminApps = mController.getActiveAdminAppsForTest(userId); + if (admins == null) { + if (actualAdminApps != null && !actualAdminApps.isEmpty()) { + fail("Admin apps should be null; " + getAdminAppsStr(userId, actualAdminApps)); + } + return; + } + assertEquals("No. of admin apps not equal; " + getAdminAppsStr(userId, actualAdminApps) + + "; expected=" + Arrays.toString(admins), admins.length, actualAdminApps.size()); + final Set<String> adminAppsCopy = new ArraySet<>(actualAdminApps); + for (String admin : admins) { + adminAppsCopy.remove(admin); + } + assertTrue("Unexpected admin apps; " + getAdminAppsStr(userId, actualAdminApps) + + "; expected=" + Arrays.toString(admins), adminAppsCopy.isEmpty()); + } + private void setActiveAdmins(int userId, String... admins) { + mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId); } } diff --git a/services/usage/java/com/android/server/usage/AppStandbyController.java b/services/usage/java/com/android/server/usage/AppStandbyController.java index 2ec218aef4e6..ff3d58652aed 100644 --- a/services/usage/java/com/android/server/usage/AppStandbyController.java +++ b/services/usage/java/com/android/server/usage/AppStandbyController.java @@ -33,7 +33,6 @@ import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY; import android.app.ActivityManager; import android.app.AppGlobals; -import android.app.admin.DevicePolicyManager; import android.app.usage.UsageStatsManager.StandbyBuckets; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener; @@ -66,13 +65,16 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.telephony.TelephonyManager; +import android.util.ArraySet; import android.util.KeyValueListParser; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; import android.view.Display; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IBatteryStats; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; @@ -87,6 +89,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; /** * Manages the standby state of an app, listening to various events. @@ -147,6 +150,9 @@ public class AppStandbyController { @GuardedBy("mAppIdleLock") private List<String> mCarrierPrivilegedApps; + @GuardedBy("mActiveAdminApps") + private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>(); + // Messages for the handler static final int MSG_INFORM_LISTENERS = 3; static final int MSG_FORCE_IDLE_STATE = 4; @@ -619,6 +625,9 @@ public class AppStandbyController { public void onUserRemoved(int userId) { synchronized (mAppIdleLock) { mAppIdleHistory.onUserRemoved(userId); + synchronized (mActiveAdminApps) { + mActiveAdminApps.remove(userId); + } } } @@ -857,10 +866,39 @@ public class AppStandbyController { newBucket); } - private boolean isActiveDeviceAdmin(String packageName, int userId) { - DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - if (dpm == null) return false; - return dpm.packageHasActiveAdmins(packageName, userId); + @VisibleForTesting + boolean isActiveDeviceAdmin(String packageName, int userId) { + synchronized (mActiveAdminApps) { + final Set<String> adminPkgs = mActiveAdminApps.get(userId); + return adminPkgs != null && adminPkgs.contains(packageName); + } + } + + public void addActiveDeviceAdmin(String adminPkg, int userId) { + synchronized (mActiveAdminApps) { + Set<String> adminPkgs = mActiveAdminApps.get(userId); + if (adminPkgs == null) { + adminPkgs = new ArraySet<>(); + mActiveAdminApps.put(userId, adminPkgs); + } + adminPkgs.add(adminPkg); + } + } + + public void setActiveAdminApps(Set<String> adminPkgs, int userId) { + synchronized (mActiveAdminApps) { + if (adminPkgs == null) { + mActiveAdminApps.remove(userId); + } else { + mActiveAdminApps.put(userId, adminPkgs); + } + } + } + + Set<String> getActiveAdminAppsForTest(int userId) { + synchronized (mActiveAdminApps) { + return mActiveAdminApps.get(userId); + } } /** diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 463a26eb8dc0..78cc81f2bab6 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -70,6 +70,7 @@ import java.io.PrintWriter; import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; /** * A service that collects, aggregates, and persists application usage data. @@ -1020,5 +1021,14 @@ public class UsageStatsService extends SystemService implements public long getTimeSinceLastJobRun(String packageName, int userId) { return mAppStandby.getTimeSinceLastJobRun(packageName, userId); } + + public void onActiveAdminAdded(String packageName, int userId) { + mAppStandby.addActiveDeviceAdmin(packageName, userId); + } + + @Override + public void setActiveAdminApps(Set<String> packageNames, int userId) { + mAppStandby.setActiveAdminApps(packageNames, userId); + } } } |