summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppTimeLimitControllerTests.java67
-rw-r--r--services/usage/java/com/android/server/usage/AppTimeLimitController.java45
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java19
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");
}