Merge "Add limits to App Usage Observer Api" into pi-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
98fd4fa0f4
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user