Merge "Add limits to App Usage Observer Api" into pi-dev
am: 98fd4fa0f4
Change-Id: I79e58fa83b389bffeb5b286cbddbbfe15de17caf
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user