diff options
4 files changed, 122 insertions, 14 deletions
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index 7fb97d3a0d22..1b0540230d66 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -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 diff --git a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java index 6b52ee5f4408..84475bb365b7 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java @@ -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; @@ -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 @@ -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 @@ -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); } diff --git a/services/usage/java/com/android/server/usage/AppTimeLimitController.java b/services/usage/java/com/android/server/usage/AppTimeLimitController.java index e20185114261..e7c54d8415d7 100644 --- a/services/usage/java/com/android/server/usage/AppTimeLimitController.java +++ b/services/usage/java/com/android/server/usage/AppTimeLimitController.java @@ -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; @@ -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<>(); @@ -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; } @@ -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); @@ -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; @@ -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); } } @@ -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); + } } } @@ -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); } } diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index f777f1da47b2..243718a526e9 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -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<>(); @@ -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()); @@ -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)); @@ -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) { @@ -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; @@ -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"); } |