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

am: 98fd4fa0f4

Change-Id: I79e58fa83b389bffeb5b286cbddbbfe15de17caf
This commit is contained in:
Amith Yamasani
2018-05-11 12:19:32 -07:00
committed by android-build-merger
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 * 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 * 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 * 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 * @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. * 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 * @param packages The list of packages to observe for foreground activity time. Cannot be null
* and must include at least one package. * and must include at least one package.
* @param timeLimit The total time the set of apps can be in the foreground before the * @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 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 * @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 * 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.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.os.HandlerThread; import android.os.HandlerThread;
@@ -49,9 +50,20 @@ public class AppTimeLimitControllerTests {
private static final int OBS_ID1 = 1; private static final int OBS_ID1 = 1;
private static final int OBS_ID2 = 2; private static final int OBS_ID2 = 2;
private static final int OBS_ID3 = 3; 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_30_MIN = 30 * 60_000L;
private static final long TIME_10_MIN = 10 * 60_1000L; 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 = { private static final String[] GROUP1 = {
PKG_SOC1, PKG_GAME1, PKG_PROD PKG_SOC1, PKG_GAME1, PKG_PROD
@@ -93,6 +105,16 @@ public class AppTimeLimitControllerTests {
protected long getUptimeMillis() { protected long getUptimeMillis() {
return mUptimeMillis; return mUptimeMillis;
} }
@Override
protected long getObserverPerUidLimit() {
return MAX_OBSERVER_PER_UID;
}
@Override
protected long getMinTimeLimit() {
return MIN_TIME_LIMIT;
}
} }
@Before @Before
@@ -233,6 +255,47 @@ public class AppTimeLimitControllerTests {
assertFalse(hasObserver(OBS_ID1)); 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) { private void moveToForeground(String packageName) {
mController.moveToForeground(packageName, "class", USER_ID); mController.moveToForeground(packageName, "class", USER_ID);
} }

View File

@@ -26,6 +26,7 @@ import android.text.TextUtils;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
@@ -58,6 +59,10 @@ public class AppTimeLimitController {
private OnLimitReachedListener mListener; private OnLimitReachedListener mListener;
private static final long MAX_OBSERVER_PER_UID = 1000;
private static final long ONE_MINUTE = 60_000L;
@GuardedBy("mLock") @GuardedBy("mLock")
private final SparseArray<UserData> mUsers = new SparseArray<>(); private final SparseArray<UserData> mUsers = new SparseArray<>();
@@ -77,6 +82,9 @@ public class AppTimeLimitController {
/** Map of observerId to details of the time limit group */ /** Map of observerId to details of the time limit group */
private SparseArray<TimeLimitGroup> groups = new SparseArray<>(); 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) { private UserData(@UserIdInt int userId) {
this.userId = userId; this.userId = userId;
} }
@@ -147,6 +155,18 @@ public class AppTimeLimitController {
return SystemClock.uptimeMillis(); 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 */ /** Returns an existing UserData object for the given userId, or creates one */
private UserData getOrCreateUserDataLocked(int userId) { private UserData getOrCreateUserDataLocked(int userId) {
UserData userData = mUsers.get(userId); UserData userData = mUsers.get(userId);
@@ -171,10 +191,20 @@ public class AppTimeLimitController {
*/ */
public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit, public void addObserver(int requestingUid, int observerId, String[] packages, long timeLimit,
PendingIntent callbackIntent, @UserIdInt int userId) { PendingIntent callbackIntent, @UserIdInt int userId) {
if (timeLimit < getMinTimeLimit()) {
throw new IllegalArgumentException("Time limit must be >= " + getMinTimeLimit());
}
synchronized (mLock) { synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId); 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(); TimeLimitGroup group = new TimeLimitGroup();
group.observerId = observerId; group.observerId = observerId;
@@ -216,7 +246,7 @@ public class AppTimeLimitController {
public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) { public void removeObserver(int requestingUid, int observerId, @UserIdInt int userId) {
synchronized (mLock) { synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(userId); UserData user = getOrCreateUserDataLocked(userId);
removeObserverLocked(user, requestingUid, observerId); removeObserverLocked(user, requestingUid, observerId, /*readding =*/ false);
} }
} }
@@ -232,12 +262,19 @@ public class AppTimeLimitController {
} }
@GuardedBy("mLock") @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); TimeLimitGroup group = user.groups.get(observerId);
if (group != null && group.requestingUid == requestingUid) { if (group != null && group.requestingUid == requestingUid) {
removeGroupFromPackageMapLocked(user, group); removeGroupFromPackageMapLocked(user, group);
user.groups.remove(observerId); user.groups.remove(observerId);
mHandler.removeMessages(MyHandler.MSG_CHECK_TIMEOUT, group); 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. // Unregister since the limit has been met and observer was informed.
synchronized (mLock) { synchronized (mLock) {
UserData user = getOrCreateUserDataLocked(group.userId); 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; PackageManagerInternal mPackageManagerInternal;
PackageMonitor mPackageMonitor; PackageMonitor mPackageMonitor;
IDeviceIdleController mDeviceIdleController; IDeviceIdleController mDeviceIdleController;
// Do not use directly. Call getDpmInternal() instead
DevicePolicyManagerInternal mDpmInternal; DevicePolicyManagerInternal mDpmInternal;
private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>(); private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
@@ -159,7 +160,6 @@ public class UsageStatsService extends SystemService implements
mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE); mUserManager = (UserManager) getContext().getSystemService(Context.USER_SERVICE);
mPackageManager = getContext().getPackageManager(); mPackageManager = getContext().getPackageManager();
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mHandler = new H(BackgroundThread.get().getLooper()); mHandler = new H(BackgroundThread.get().getLooper());
mAppStandby = new AppStandbyController(getContext(), 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) { public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) { if (phase == PHASE_SYSTEM_SERVICES_READY) {
mAppStandby.onBootPhase(phase); mAppStandby.onBootPhase(phase);
// initialize mDpmInternal
getDpmInternal();
mDeviceIdleController = IDeviceIdleController.Stub.asInterface( mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); 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 { private class UserActionsReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
@@ -675,9 +684,10 @@ public class UsageStatsService extends SystemService implements
private boolean hasObserverPermission(String callingPackage) { private boolean hasObserverPermission(String callingPackage) {
final int callingUid = Binder.getCallingUid(); final int callingUid = Binder.getCallingUid();
DevicePolicyManagerInternal dpmInternal = getDpmInternal();
if (callingUid == Process.SYSTEM_UID if (callingUid == Process.SYSTEM_UID
|| (mDpmInternal != null || (dpmInternal != null
&& mDpmInternal.isActiveAdminWithPolicy(callingUid, && dpmInternal.isActiveAdminWithPolicy(callingUid,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) { DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))) {
// Caller is the system or the profile owner, so proceed. // Caller is the system or the profile owner, so proceed.
return true; return true;
@@ -1042,9 +1052,6 @@ public class UsageStatsService extends SystemService implements
if (packages == null || packages.length == 0) { if (packages == null || packages.length == 0) {
throw new IllegalArgumentException("Must specify at least one package"); throw new IllegalArgumentException("Must specify at least one package");
} }
if (timeLimitMs <= 0) {
throw new IllegalArgumentException("Time limit must be > 0");
}
if (callbackIntent == null) { if (callbackIntent == null) {
throw new NullPointerException("callbackIntent can't be null"); throw new NullPointerException("callbackIntent can't be null");
} }