Update DPMS to push active admins info to UsageStatsService.
Bug: 71710099
Test: atest services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
Test: atest services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
Change-Id: Ia46be9008470b0228978306b9992560fc4f2c586
diff --git a/core/java/android/app/usage/UsageStatsManagerInternal.java b/core/java/android/app/usage/UsageStatsManagerInternal.java
index 93e14de..5cd0981 100644
--- a/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -22,6 +22,7 @@
import android.content.res.Configuration;
import java.util.List;
+import java.util.Set;
/**
* UsageStatsManager local system service interface.
@@ -159,6 +160,22 @@
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 c1e95eb..7d64e43 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 @@
*/
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 f0681e9..3c6187b 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.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 @@
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 @@
@Override
public void onStart() {
publishBinderService(Context.DEVICE_POLICY_SERVICE, mService);
+ mService.handleStart();
}
@Override
@@ -1560,6 +1563,7 @@
removedAdmin = true;
policy.mAdminList.remove(i);
policy.mAdminMap.remove(aa.info.getComponent());
+ pushActiveAdminPackagesLocked(userHandle);
}
}
} catch (RemoteException re) {
@@ -1653,6 +1657,10 @@
return LocalServices.getService(PackageManagerInternal.class);
}
+ UsageStatsManagerInternal getUsageStatsManagerInternal() {
+ return LocalServices.getService(UsageStatsManagerInternal.class);
+ }
+
NotificationManager getNotificationManager() {
return mContext.getSystemService(NotificationManager.class);
}
@@ -1923,6 +1931,8 @@
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 @@
}
@Override
+ void handleStart() {
+ pushActiveAdminPackages();
+ }
+
+ @Override
void handleStartUser(int userId) {
updateScreenCaptureDisabledInWindowManager(userId,
getScreenCaptureDisabled(null, userId));
@@ -3357,6 +3372,8 @@
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 @@
}
}
+ 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 @@
}
}
+ @Override
public void forceRemoveActiveAdmin(ComponentName adminReceiver, int userHandle) {
if (!mHasFeature) {
return;
@@ -3540,7 +3587,7 @@
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 @@
}
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 @@
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 0650acb..f5894e0 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.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 @@
}
@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 58ac7d2..57030e4 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.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 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 @@
initializeDpms();
+ Mockito.reset(getServices().usageStatsManagerInternal);
setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin2, DpmMockContext.CALLER_UID);
setUpPackageManagerForAdmin(admin3, DpmMockContext.CALLER_UID);
@@ -207,6 +210,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
() -> 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 @@
dpm.removeActiveAdmin(admin1);
assertFalse(dpm.isAdminActiveAsUser(admin1, DpmMockContext.CALLER_USER_HANDLE));
+ verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
+ null, DpmMockContext.CALLER_USER_HANDLE);
}
/**
@@ -547,6 +593,8 @@
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 @@
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 @@
// 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 @@
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 @@
// 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 4232c44..11e32f8 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.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 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 @@
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 e43786c..288a8be 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.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 @@
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 725fb21..40964c0 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.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.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 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 @@
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 @@
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 2ec218a..ff3d586 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 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.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.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 @@
@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 void onUserRemoved(int userId) {
synchronized (mAppIdleLock) {
mAppIdleHistory.onUserRemoved(userId);
+ synchronized (mActiveAdminApps) {
+ mActiveAdminApps.remove(userId);
+ }
}
}
@@ -857,10 +866,39 @@
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 463a26e..78cc81f 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.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 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);
+ }
}
}