Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 98fd4fa0 authored by Amith Yamasani's avatar Amith Yamasani Committed by Android (Google) Code Review
Browse files

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

parents 1c60953f c8703099
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -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
+65 −2
Original line number Diff line number Diff line
@@ -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);
    }
+41 −4
Original line number Diff line number Diff line
@@ -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);
        }
    }

+13 −6
Original line number Diff line number Diff line
@@ -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");
            }