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

Commit 36424476 authored by Amith Yamasani's avatar Amith Yamasani Committed by android-build-merger
Browse files

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

am: 98fd4fa0

Change-Id: I79e58fa83b389bffeb5b286cbddbbfe15de17caf
parents 50b2c1ae 98fd4fa0
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");
            }