Loading core/java/android/app/usage/UsageStatsManager.java +3 −2 Original line number Diff line number Diff line Loading @@ -572,13 +572,14 @@ public final class UsageStatsManager { * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The * observer will automatically be unregistered when the time limit is reached and the intent * is delivered. Registering an {@code observerId} that was already registered will override * the previous one. * the previous one. No more than 1000 unique {@code observerId} may be registered by a single * uid at any one time. * @param observerId A unique id associated with the group of apps to be monitored. There can * be multiple groups with common packages and different time limits. * @param packages The list of packages to observe for foreground activity time. Cannot be null * and must include at least one package. * @param timeLimit The total time the set of apps can be in the foreground before the * callbackIntent is delivered. Must be greater than 0. * callbackIntent is delivered. Must be at least one minute. * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. * @param callbackIntent The PendingIntent that will be dispatched when the time limit is * exceeded by the group of apps. The delivered Intent will also contain Loading services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +65 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.usage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PendingIntent; import android.os.HandlerThread; Loading Loading @@ -49,9 +50,20 @@ public class AppTimeLimitControllerTests { private static final int OBS_ID1 = 1; private static final int OBS_ID2 = 2; private static final int OBS_ID3 = 3; private static final int OBS_ID4 = 4; private static final int OBS_ID5 = 5; private static final int OBS_ID6 = 6; private static final int OBS_ID7 = 7; private static final int OBS_ID8 = 8; private static final int OBS_ID9 = 9; private static final int OBS_ID10 = 10; private static final int OBS_ID11 = 11; private static final long TIME_30_MIN = 30 * 60_1000L; private static final long TIME_10_MIN = 10 * 60_1000L; private static final long TIME_30_MIN = 30 * 60_000L; private static final long TIME_10_MIN = 10 * 60_000L; private static final long MAX_OBSERVER_PER_UID = 10; private static final long MIN_TIME_LIMIT = 4_000L; private static final String[] GROUP1 = { PKG_SOC1, PKG_GAME1, PKG_PROD Loading Loading @@ -93,6 +105,16 @@ public class AppTimeLimitControllerTests { protected long getUptimeMillis() { return mUptimeMillis; } @Override protected long getObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } @Override protected long getMinTimeLimit() { return MIN_TIME_LIMIT; } } @Before Loading Loading @@ -233,6 +255,47 @@ public class AppTimeLimitControllerTests { assertFalse(hasObserver(OBS_ID1)); } /** Verify that App Time Limit Controller will limit the number of observerIds */ @Test public void testMaxObserverLimit() throws Exception { boolean receivedException = false; int ANOTHER_UID = UID + 1; addObserver(OBS_ID1, GROUP1, TIME_30_MIN); addObserver(OBS_ID2, GROUP1, TIME_30_MIN); addObserver(OBS_ID3, GROUP1, TIME_30_MIN); addObserver(OBS_ID4, GROUP1, TIME_30_MIN); addObserver(OBS_ID5, GROUP1, TIME_30_MIN); addObserver(OBS_ID6, GROUP1, TIME_30_MIN); addObserver(OBS_ID7, GROUP1, TIME_30_MIN); addObserver(OBS_ID8, GROUP1, TIME_30_MIN); addObserver(OBS_ID9, GROUP1, TIME_30_MIN); addObserver(OBS_ID10, GROUP1, TIME_30_MIN); // Readding an observer should not cause an IllegalStateException addObserver(OBS_ID5, GROUP1, TIME_30_MIN); // Adding an observer for a different uid shouldn't cause an IllegalStateException mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID); try { addObserver(OBS_ID11, GROUP1, TIME_30_MIN); } catch (IllegalStateException ise) { receivedException = true; } assertTrue("Should have caused an IllegalStateException", receivedException); } /** Verify that addObserver minimum time limit is one minute */ @Test public void testMinimumTimeLimit() throws Exception { boolean receivedException = false; // adding an observer with a one minute time limit should not cause an exception addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT); try { addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1); } catch (IllegalArgumentException iae) { receivedException = true; } assertTrue("Should have caused an IllegalArgumentException", receivedException); } private void moveToForeground(String packageName) { mController.moveToForeground(packageName, "class", USER_ID); } Loading services/usage/java/com/android/server/usage/AppTimeLimitController.java +41 −4 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -58,6 +59,10 @@ public class AppTimeLimitController { private OnLimitReachedListener mListener; private static final long MAX_OBSERVER_PER_UID = 1000; private static final long ONE_MINUTE = 60_000L; @GuardedBy("mLock") private final SparseArray<UserData> mUsers = new SparseArray<>(); Loading @@ -77,6 +82,9 @@ public class AppTimeLimitController { /** Map of observerId to details of the time limit group */ private SparseArray<TimeLimitGroup> groups = new SparseArray<>(); /** Map of the number of observerIds registered by uid */ private SparseIntArray observerIdCounts = new SparseIntArray(); private UserData(@UserIdInt int userId) { this.userId = userId; } Loading Loading @@ -147,6 +155,18 @@ public class AppTimeLimitController { return SystemClock.uptimeMillis(); } /** Overrideable for testing purposes */ @VisibleForTesting protected long getObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } /** Overrideable for testing purposes */ @VisibleForTesting protected long getMinTimeLimit() { return ONE_MINUTE; } /** Returns an existing UserData object for the given userId, or creates one */ private UserData getOrCreateUserDataLocked(int userId) { UserData userData = mUsers.get(userId); Loading @@ -171,10 +191,20 @@ public class AppTimeLimitController { */ public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) { if (timeLimit < getMinTimeLimit()) { throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit()); } synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true); removeObserverLocked(user, requestingUid, observerId); final int observerIdCount = user.observerIdCounts.get(requestingUid, 0); if (observerIdCount >= getObserverPerUidLimit()) { throw new IllegalStateException( "Too many observers added by uid " + requestingUid); } user.observerIdCounts.put(requestingUid, observerIdCount + 1); TimeLimitGroup group = new TimeLimitGroup(); group.observerId = observerId; Loading Loading @@ -216,7 +246,7 @@ public class AppTimeLimitController { public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) { synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); removeObserverLocked(user, requestingUid, observerId); removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false); } } Loading @@ -232,12 +262,19 @@ public class AppTimeLimitController { } @GuardedBy("mLock") private void removeObserverLocked(UserData user, int requestingUid, int observerId) { private void removeObserverLocked(UserData user, int requestingUid, int observerId, boolean readding) { TimeLimitGroup group = user.groups.get(observerId); if (group != null && group.requestingUid == requestingUid) { removeGroupFromPackageMapLocked(user, group); user.groups.remove(observerId); mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); final int observerIdCount = user.observerIdCounts.get(requestingUid); if (observerIdCount <= 1 && !readding) { user.observerIdCounts.delete(requestingUid); } else { user.observerIdCounts.put(requestingUid, observerIdCount - 1); } } } Loading Loading @@ -321,7 +358,7 @@ public class AppTimeLimitController { // Unregister since the limit has been met and observer was informed. synchronized (mLock) { UserData user = getOrCreateUserDataLocked(group.userId); removeObserverLocked(user, group.requestingUid, group.observerId); removeObserverLocked(user, group.requestingUid, group.observerId, false); } } Loading services/usage/java/com/android/server/usage/UsageStatsService.java +13 −6 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ public class UsageStatsService extends SystemService implements PackageManagerInternal mPackageManagerInternal; PackageMonitor mPackageMonitor; IDeviceIdleController mDeviceIdleController; // Do not use directly. Call getDpmInternal() instead DevicePolicyManagerInternal mDpmInternal; private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); Loading Loading @@ -159,7 +160,6 @@ public class UsageStatsService extends SystemService implements mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mPackageManager = getContext().getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mHandler = new H(BackgroundThread.get().getLooper()); mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper()); Loading Loading @@ -209,6 +209,8 @@ public class UsageStatsService extends SystemService implements public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { mAppStandby.onBootPhase(phase); // initialize mDpmInternal getDpmInternal(); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); Loading @@ -228,6 +230,13 @@ public class UsageStatsService extends SystemService implements } } private DevicePolicyManagerInternal getDpmInternal() { if (mDpmInternal == null) { mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); } return mDpmInternal; } private class UserActionsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -675,9 +684,10 @@ public class UsageStatsService extends SystemService implements private boolean hasObserverPermission(String callingPackage) { final int callingUid = Binder.getCallingUid(); DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (callingUid == Process.SYSTEM_UID || (mDpmInternal != null && mDpmInternal.isActiveAdminWithPolicy(callingUid, || (dpmInternal != null && dpmInternal.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) { // Caller is the system or the profile owner, so proceed. return true; Loading Loading @@ -1042,9 +1052,6 @@ public class UsageStatsService extends SystemService implements if (packages == null || packages.length == 0) { throw new IllegalArgumentException("Must specify at least one package"); } if (timeLimitMs <= 0) { throw new IllegalArgumentException("Time limit must be > 0"); } if (callbackIntent == null) { throw new NullPointerException("callbackIntent can't be null"); } Loading Loading
core/java/android/app/usage/UsageStatsManager.java +3 −2 Original line number Diff line number Diff line Loading @@ -572,13 +572,14 @@ public final class UsageStatsManager { * the sum of usages of apps in the packages array exceeds the {@code timeLimit} specified. The * observer will automatically be unregistered when the time limit is reached and the intent * is delivered. Registering an {@code observerId} that was already registered will override * the previous one. * the previous one. No more than 1000 unique {@code observerId} may be registered by a single * uid at any one time. * @param observerId A unique id associated with the group of apps to be monitored. There can * be multiple groups with common packages and different time limits. * @param packages The list of packages to observe for foreground activity time. Cannot be null * and must include at least one package. * @param timeLimit The total time the set of apps can be in the foreground before the * callbackIntent is delivered. Must be greater than 0. * callbackIntent is delivered. Must be at least one minute. * @param timeUnit The unit for time specified in {@code timeLimit}. Cannot be null. * @param callbackIntent The PendingIntent that will be dispatched when the time limit is * exceeded by the group of apps. The delivered Intent will also contain Loading
services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +65 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.usage; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.PendingIntent; import android.os.HandlerThread; Loading Loading @@ -49,9 +50,20 @@ public class AppTimeLimitControllerTests { private static final int OBS_ID1 = 1; private static final int OBS_ID2 = 2; private static final int OBS_ID3 = 3; private static final int OBS_ID4 = 4; private static final int OBS_ID5 = 5; private static final int OBS_ID6 = 6; private static final int OBS_ID7 = 7; private static final int OBS_ID8 = 8; private static final int OBS_ID9 = 9; private static final int OBS_ID10 = 10; private static final int OBS_ID11 = 11; private static final long TIME_30_MIN = 30 * 60_1000L; private static final long TIME_10_MIN = 10 * 60_1000L; private static final long TIME_30_MIN = 30 * 60_000L; private static final long TIME_10_MIN = 10 * 60_000L; private static final long MAX_OBSERVER_PER_UID = 10; private static final long MIN_TIME_LIMIT = 4_000L; private static final String[] GROUP1 = { PKG_SOC1, PKG_GAME1, PKG_PROD Loading Loading @@ -93,6 +105,16 @@ public class AppTimeLimitControllerTests { protected long getUptimeMillis() { return mUptimeMillis; } @Override protected long getObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } @Override protected long getMinTimeLimit() { return MIN_TIME_LIMIT; } } @Before Loading Loading @@ -233,6 +255,47 @@ public class AppTimeLimitControllerTests { assertFalse(hasObserver(OBS_ID1)); } /** Verify that App Time Limit Controller will limit the number of observerIds */ @Test public void testMaxObserverLimit() throws Exception { boolean receivedException = false; int ANOTHER_UID = UID + 1; addObserver(OBS_ID1, GROUP1, TIME_30_MIN); addObserver(OBS_ID2, GROUP1, TIME_30_MIN); addObserver(OBS_ID3, GROUP1, TIME_30_MIN); addObserver(OBS_ID4, GROUP1, TIME_30_MIN); addObserver(OBS_ID5, GROUP1, TIME_30_MIN); addObserver(OBS_ID6, GROUP1, TIME_30_MIN); addObserver(OBS_ID7, GROUP1, TIME_30_MIN); addObserver(OBS_ID8, GROUP1, TIME_30_MIN); addObserver(OBS_ID9, GROUP1, TIME_30_MIN); addObserver(OBS_ID10, GROUP1, TIME_30_MIN); // Readding an observer should not cause an IllegalStateException addObserver(OBS_ID5, GROUP1, TIME_30_MIN); // Adding an observer for a different uid shouldn't cause an IllegalStateException mController.addObserver(ANOTHER_UID, OBS_ID11, GROUP1, TIME_30_MIN, null, USER_ID); try { addObserver(OBS_ID11, GROUP1, TIME_30_MIN); } catch (IllegalStateException ise) { receivedException = true; } assertTrue("Should have caused an IllegalStateException", receivedException); } /** Verify that addObserver minimum time limit is one minute */ @Test public void testMinimumTimeLimit() throws Exception { boolean receivedException = false; // adding an observer with a one minute time limit should not cause an exception addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT); try { addObserver(OBS_ID1, GROUP1, MIN_TIME_LIMIT - 1); } catch (IllegalArgumentException iae) { receivedException = true; } assertTrue("Should have caused an IllegalArgumentException", receivedException); } private void moveToForeground(String packageName) { mController.moveToForeground(packageName, "class", USER_ID); } Loading
services/usage/java/com/android/server/usage/AppTimeLimitController.java +41 −4 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -58,6 +59,10 @@ public class AppTimeLimitController { private OnLimitReachedListener mListener; private static final long MAX_OBSERVER_PER_UID = 1000; private static final long ONE_MINUTE = 60_000L; @GuardedBy("mLock") private final SparseArray<UserData> mUsers = new SparseArray<>(); Loading @@ -77,6 +82,9 @@ public class AppTimeLimitController { /** Map of observerId to details of the time limit group */ private SparseArray<TimeLimitGroup> groups = new SparseArray<>(); /** Map of the number of observerIds registered by uid */ private SparseIntArray observerIdCounts = new SparseIntArray(); private UserData(@UserIdInt int userId) { this.userId = userId; } Loading Loading @@ -147,6 +155,18 @@ public class AppTimeLimitController { return SystemClock.uptimeMillis(); } /** Overrideable for testing purposes */ @VisibleForTesting protected long getObserverPerUidLimit() { return MAX_OBSERVER_PER_UID; } /** Overrideable for testing purposes */ @VisibleForTesting protected long getMinTimeLimit() { return ONE_MINUTE; } /** Returns an existing UserData object for the given userId, or creates one */ private UserData getOrCreateUserDataLocked(int userId) { UserData userData = mUsers.get(userId); Loading @@ -171,10 +191,20 @@ public class AppTimeLimitController { */ public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit, PendingIntent callbackIntent, @UserIdInt int userId) { if (timeLimit < getMinTimeLimit()) { throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit()); } synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); removeObserverLocked(user, requestingUid, observerId, /*readding =*/ true); removeObserverLocked(user, requestingUid, observerId); final int observerIdCount = user.observerIdCounts.get(requestingUid, 0); if (observerIdCount >= getObserverPerUidLimit()) { throw new IllegalStateException( "Too many observers added by uid " + requestingUid); } user.observerIdCounts.put(requestingUid, observerIdCount + 1); TimeLimitGroup group = new TimeLimitGroup(); group.observerId = observerId; Loading Loading @@ -216,7 +246,7 @@ public class AppTimeLimitController { public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) { synchronized (mLock) { UserData user = getOrCreateUserDataLocked(userId); removeObserverLocked(user, requestingUid, observerId); removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false); } } Loading @@ -232,12 +262,19 @@ public class AppTimeLimitController { } @GuardedBy("mLock") private void removeObserverLocked(UserData user, int requestingUid, int observerId) { private void removeObserverLocked(UserData user, int requestingUid, int observerId, boolean readding) { TimeLimitGroup group = user.groups.get(observerId); if (group != null && group.requestingUid == requestingUid) { removeGroupFromPackageMapLocked(user, group); user.groups.remove(observerId); mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); final int observerIdCount = user.observerIdCounts.get(requestingUid); if (observerIdCount <= 1 && !readding) { user.observerIdCounts.delete(requestingUid); } else { user.observerIdCounts.put(requestingUid, observerIdCount - 1); } } } Loading Loading @@ -321,7 +358,7 @@ public class AppTimeLimitController { // Unregister since the limit has been met and observer was informed. synchronized (mLock) { UserData user = getOrCreateUserDataLocked(group.userId); removeObserverLocked(user, group.requestingUid, group.observerId); removeObserverLocked(user, group.requestingUid, group.observerId, false); } } Loading
services/usage/java/com/android/server/usage/UsageStatsService.java +13 −6 Original line number Diff line number Diff line Loading @@ -115,6 +115,7 @@ public class UsageStatsService extends SystemService implements PackageManagerInternal mPackageManagerInternal; PackageMonitor mPackageMonitor; IDeviceIdleController mDeviceIdleController; // Do not use directly. Call getDpmInternal() instead DevicePolicyManagerInternal mDpmInternal; private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); Loading Loading @@ -159,7 +160,6 @@ public class UsageStatsService extends SystemService implements mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mPackageManager = getContext().getPackageManager(); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); mHandler = new H(BackgroundThread.get().getLooper()); mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper()); Loading Loading @@ -209,6 +209,8 @@ public class UsageStatsService extends SystemService implements public void onBootPhase(int phase) { if (phase == PHASE_SYSTEM_SERVICES_READY) { mAppStandby.onBootPhase(phase); // initialize mDpmInternal getDpmInternal(); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); Loading @@ -228,6 +230,13 @@ public class UsageStatsService extends SystemService implements } } private DevicePolicyManagerInternal getDpmInternal() { if (mDpmInternal == null) { mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class); } return mDpmInternal; } private class UserActionsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Loading Loading @@ -675,9 +684,10 @@ public class UsageStatsService extends SystemService implements private boolean hasObserverPermission(String callingPackage) { final int callingUid = Binder.getCallingUid(); DevicePolicyManagerInternal dpmInternal = getDpmInternal(); if (callingUid == Process.SYSTEM_UID || (mDpmInternal != null && mDpmInternal.isActiveAdminWithPolicy(callingUid, || (dpmInternal != null && dpmInternal.isActiveAdminWithPolicy(callingUid, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) { // Caller is the system or the profile owner, so proceed. return true; Loading Loading @@ -1042,9 +1052,6 @@ public class UsageStatsService extends SystemService implements if (packages == null || packages.length == 0) { throw new IllegalArgumentException("Must specify at least one package"); } if (timeLimitMs <= 0) { throw new IllegalArgumentException("Time limit must be > 0"); } if (callbackIntent == null) { throw new NullPointerException("callbackIntent can't be null"); } Loading