Merge "Add limits to App Usage Observer Api" into pi-dev

This commit is contained in:
Amith Yamasani
2018-05-11 19:03:11 +00:00
committed by Android (Google) Code Review
4 changed files with 122 additions and 14 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}

View File

@@ -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");
}