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

Commit 61864145 authored by Evan Severson's avatar Evan Severson
Browse files

Watch uid proc state instead of importance for 1-time permissions

The system process may bind to an app with the flag
BIND_FOREGROUND_SERVICE, this will put the client in the foreground
service importance level without the normal requirement that foreground
services must show a notification. Looking at proc states instead allows
us to differentiate between these two levels of foreground service and
revoke the client when not in use.

This change makes the parameters `importanceToResetTimer` and
`importanceToKeepSessionAlive` in PermissionManager#startOneTimePermissionSession
obsolete.

Test: atest CtsPermissionTestCases + manual testing with mic/cam/loc
Bug: 217981062

Change-Id: I7a725647c001062d1a76a82b680a02e3e2edcb03
parent 026b01f0
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -77,8 +77,7 @@ interface IPermissionManager {
    List<SplitPermissionInfoParcelable> getSplitPermissions();

    void startOneTimePermissionSession(String packageName, int userId, long timeout,
            long revokeAfterKilledDelay, int importanceToResetTimer,
            int importanceToKeepSessionAlive);
            long revokeAfterKilledDelay);

    @EnforcePermission("MANAGE_ONE_TIME_PERMISSION_SESSIONS")
    void stopOneTimePermissionSession(String packageName, int userId);
+1 −2
Original line number Diff line number Diff line
@@ -1371,8 +1371,7 @@ public final class PermissionManager {
            @ActivityManager.RunningAppProcessInfo.Importance int importanceToKeepSessionAlive) {
        try {
            mPermissionManager.startOneTimePermissionSession(packageName, mContext.getUserId(),
                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
                    importanceToKeepSessionAlive);
                    timeoutMillis, revokeAfterKilledDelayMillis);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
+112 −119
Original line number Diff line number Diff line
@@ -16,17 +16,18 @@

package com.android.server.pm.permission;

import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;

import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.IUidObserver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.RemoteException;
import android.permission.PermissionControllerManager;
import android.provider.DeviceConfig;
import android.util.Log;
@@ -48,7 +49,7 @@ public class OneTimePermissionUserManager {
            "one_time_permissions_killed_delay_millis";

    private final @NonNull Context mContext;
    private final @NonNull ActivityManager mActivityManager;
    private final @NonNull IActivityManager mIActivityManager;
    private final @NonNull AlarmManager mAlarmManager;
    private final @NonNull PermissionControllerManager mPermissionControllerManager;

@@ -78,50 +79,15 @@ public class OneTimePermissionUserManager {

    OneTimePermissionUserManager(@NonNull Context context) {
        mContext = context;
        mActivityManager = context.getSystemService(ActivityManager.class);
        mIActivityManager = ActivityManager.getService();
        mAlarmManager = context.getSystemService(AlarmManager.class);
        mPermissionControllerManager = new PermissionControllerManager(
                mContext, PermissionThread.getHandler());
        mHandler = context.getMainThreadHandler();
    }

    /**
     * Starts a one-time permission session for a given package. A one-time permission session is
     * ended if app becomes inactive. Inactivity is defined as the package's uid importance level
     * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid
     * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start
     * until going > importanceToResetTimer.
     * <p>
     * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive
     * then the session is extended until either the importance goes above
     * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which
     * will continue the session and reset the timer.
     * </p>
     * <p>
     * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}.
     * </p>
     * <p>
     * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout
     * is invoked.
     * </p>
     * <p>
     * Note that if there is currently an active session for a package a new one isn't created and
     * the existing one isn't changed.
     * </p>
     * @param packageName The package to start a one-time permission session for
     * @param timeoutMillis Number of milliseconds for an app to be in an inactive state
     * @param revokeAfterKilledDelayMillis Number of milliseconds to wait after the process dies
     *                                     before ending the session. Set to -1 to use default value
     *                                     for the device.
     * @param importanceToResetTimer The least important level to uid must be to reset the timer
     * @param importanceToKeepSessionAlive The least important level the uid must be to keep the
     *                                     session alive
     *
     * @hide
     */
    void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis,
            long revokeAfterKilledDelayMillis, int importanceToResetTimer,
            int importanceToKeepSessionAlive) {
            long revokeAfterKilledDelayMillis) {
        int uid;
        try {
            uid = mContext.getPackageManager().getPackageUid(packageName, 0);
@@ -133,13 +99,11 @@ public class OneTimePermissionUserManager {
        synchronized (mLock) {
            PackageInactivityListener listener = mListeners.get(uid);
            if (listener != null) {
                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis,
                        importanceToResetTimer, importanceToKeepSessionAlive);
                listener.updateSessionParameters(timeoutMillis, revokeAfterKilledDelayMillis);
                return;
            }
            listener = new PackageInactivityListener(uid, packageName, timeoutMillis,
                    revokeAfterKilledDelayMillis, importanceToResetTimer,
                    importanceToKeepSessionAlive);
                    revokeAfterKilledDelayMillis);
            mListeners.put(uid, listener);
        }
    }
@@ -184,34 +148,58 @@ public class OneTimePermissionUserManager {

        private static final long TIMER_INACTIVE = -1;

        private static final int STATE_GONE = 0;
        private static final int STATE_TIMER = 1;
        private static final int STATE_ACTIVE = 2;

        private final int mUid;
        private final @NonNull String mPackageName;
        private long mTimeout;
        private long mRevokeAfterKilledDelay;
        private int mImportanceToResetTimer;
        private int mImportanceToKeepSessionAlive;

        private boolean mIsAlarmSet;
        private boolean mIsFinished;

        private long mTimerStart = TIMER_INACTIVE;

        private final ActivityManager.OnUidImportanceListener mStartTimerListener;
        private final ActivityManager.OnUidImportanceListener mSessionKillableListener;
        private final ActivityManager.OnUidImportanceListener mGoneListener;

        private final Object mInnerLock = new Object();
        private final Object mToken = new Object();
        private final IUidObserver.Stub mObserver = new IUidObserver.Stub() {
            @Override
            public void onUidGone(int uid, boolean disabled) {
                if (uid == mUid) {
                    PackageInactivityListener.this.updateUidState(STATE_GONE);
                }
            }

        private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
                long revokeAfterkilledDelay, int importanceToResetTimer,
                int importanceToKeepSessionAlive) {
            @Override
            public void onUidStateChanged(int uid, int procState, long procStateSeq,
                    int capability) {
                if (uid == mUid) {
                    if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
                            && procState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                        PackageInactivityListener.this.updateUidState(STATE_TIMER);
                    } else {
                        PackageInactivityListener.this.updateUidState(STATE_ACTIVE);
                    }
                }
            }

            public void onUidActive(int uid) {
            }
            public void onUidIdle(int uid, boolean disabled) {
            }
            public void onUidProcAdjChanged(int uid) {
            }
            public void onUidCachedChanged(int uid, boolean cached) {
            }
        };

        private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
                long revokeAfterkilledDelay) {
            Log.i(LOG_TAG,
                    "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
                            + " killedDelay=" + revokeAfterkilledDelay
                            + " importanceToResetTimer=" + importanceToResetTimer
                            + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
                            + " killedDelay=" + revokeAfterkilledDelay);

            mUid = uid;
            mPackageName = packageName;
@@ -221,27 +209,24 @@ public class OneTimePermissionUserManager {
                            DeviceConfig.NAMESPACE_PERMISSIONS, PROPERTY_KILLED_DELAY_CONFIG_KEY,
                            DEFAULT_KILLED_DELAY_MILLIS)
                    : revokeAfterkilledDelay;
            mImportanceToResetTimer = importanceToResetTimer;
            mImportanceToKeepSessionAlive = importanceToKeepSessionAlive;

            mStartTimerListener =
                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
            mSessionKillableListener =
                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);
            mGoneListener =
                    (changingUid, importance) -> onImportanceChanged(changingUid, importance);

            mActivityManager.addOnUidImportanceListener(mStartTimerListener,
                    importanceToResetTimer);
            mActivityManager.addOnUidImportanceListener(mSessionKillableListener,
                    importanceToKeepSessionAlive);
            mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED);
            try {
                mIActivityManager.registerUidObserver(mObserver,
                        ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_PROCSTATE,
                        ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
                        null);
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
                // Can't register uid observer, just revoke immediately
                synchronized (mInnerLock) {
                    onPackageInactiveLocked();
                }
            }

            onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName));
            updateUidState();
        }

        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis,
                int importanceToResetTimer, int importanceToKeepSessionAlive) {
        public void updateSessionParameters(long timeoutMillis, long revokeAfterKilledDelayMillis) {
            synchronized (mInnerLock) {
                mTimeout = Math.min(mTimeout, timeoutMillis);
                mRevokeAfterKilledDelay = Math.min(mRevokeAfterKilledDelay,
@@ -250,63 +235,79 @@ public class OneTimePermissionUserManager {
                                DeviceConfig.NAMESPACE_PERMISSIONS,
                                PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS)
                                : revokeAfterKilledDelayMillis);
                mImportanceToResetTimer = Math.min(importanceToResetTimer, mImportanceToResetTimer);
                mImportanceToKeepSessionAlive = Math.min(importanceToKeepSessionAlive,
                        mImportanceToKeepSessionAlive);
                Log.v(LOG_TAG,
                        "Updated params for " + mPackageName + ". timeout=" + mTimeout
                                + " killedDelay=" + mRevokeAfterKilledDelay
                                + " importanceToResetTimer=" + mImportanceToResetTimer
                                + " importanceToKeepSessionAlive=" + mImportanceToKeepSessionAlive);
                onImportanceChanged(mUid, mActivityManager.getPackageImportance(mPackageName));
                                + " killedDelay=" + mRevokeAfterKilledDelay);
                updateUidState();
            }
        }

        private void onImportanceChanged(int uid, int importance) {
            if (uid != mUid) {
                return;
        private int getCurrentState() {
            try {
                return getStateFromProcState(mIActivityManager.getUidProcessState(mUid, null));
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "Couldn't check uid proc state", e);
            }
            return STATE_GONE;
        }

        private int getStateFromProcState(int procState) {
            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
                return STATE_GONE;
            } else {
                if (procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
                    return STATE_TIMER;
                } else {
                    return STATE_ACTIVE;
                }
            }
        }

        private void updateUidState() {
            updateUidState(getCurrentState());
        }

            Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
                    + " importance=" + importance);
        private void updateUidState(int state) {
            Log.v(LOG_TAG, "Updating state for " + mPackageName + " (" + mUid + ")."
                    + " state=" + state);
            synchronized (mInnerLock) {
                // Remove any pending inactivity callback
                mHandler.removeCallbacksAndMessages(mToken);

                if (importance > IMPORTANCE_CACHED) {
                if (state == STATE_GONE) {
                    if (mRevokeAfterKilledDelay == 0) {
                        onPackageInactiveLocked();
                        return;
                    }
                    // Delay revocation in case app is restarting
                    mHandler.postDelayed(() -> {
                        int imp = mActivityManager.getUidImportance(mUid);
                        if (imp > IMPORTANCE_CACHED) {
                        int currentState;
                        synchronized (mInnerLock) {
                            currentState = getCurrentState();
                            if (currentState == STATE_GONE) {
                                onPackageInactiveLocked();
                        } else {
                                return;
                            }
                        }
                        if (DEBUG) {
                            Log.d(LOG_TAG, "No longer gone after delayed revocation. "
                                        + "Rechecking for " + mPackageName + " (" + mUid + ").");
                            }
                            onImportanceChanged(mUid, imp);
                                    + "Rechecking for " + mPackageName + " (" + mUid
                                    + ").");
                        }
                        updateUidState(currentState);
                    }, mToken, mRevokeAfterKilledDelay);
                    return;
                }
                if (importance > mImportanceToResetTimer) {
                } else if (state == STATE_TIMER) {
                    if (mTimerStart == TIMER_INACTIVE) {
                        if (DEBUG) {
                            Log.d(LOG_TAG, "Start the timer for "
                                    + mPackageName + " (" + mUid + ").");
                        }
                        mTimerStart = System.currentTimeMillis();
                        setAlarmLocked();
                    }
                } else {
                } else if (state == STATE_ACTIVE) {
                    mTimerStart = TIMER_INACTIVE;
                }
                if (importance > mImportanceToKeepSessionAlive) {
                    setAlarmLocked();
                } else {
                    cancelAlarmLocked();
                }
            }
@@ -320,19 +321,9 @@ public class OneTimePermissionUserManager {
                mIsFinished = true;
                cancelAlarmLocked();
                try {
                    mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
                } catch (IllegalArgumentException e) {
                    Log.e(LOG_TAG, "Could not remove start timer listener", e);
                }
                try {
                    mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
                } catch (IllegalArgumentException e) {
                    Log.e(LOG_TAG, "Could not remove session killable listener", e);
                }
                try {
                    mActivityManager.removeOnUidImportanceListener(mGoneListener);
                } catch (IllegalArgumentException e) {
                    Log.e(LOG_TAG, "Could not remove gone listener", e);
                    mIActivityManager.unregisterUidObserver(mObserver);
                } catch (RemoteException e) {
                    Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
                }
            }
        }
@@ -396,9 +387,11 @@ public class OneTimePermissionUserManager {
                        mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
                                mPackageName);
                    });
            mActivityManager.removeOnUidImportanceListener(mStartTimerListener);
            mActivityManager.removeOnUidImportanceListener(mSessionKillableListener);
            mActivityManager.removeOnUidImportanceListener(mGoneListener);
            try {
                mIActivityManager.unregisterUidObserver(mObserver);
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "Unable to unregister uid observer.", e);
            }
            synchronized (mLock) {
                mListeners.remove(mUid);
            }
+2 −4
Original line number Diff line number Diff line
@@ -385,8 +385,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {

    @Override
    public void startOneTimePermissionSession(String packageName, @UserIdInt int userId,
            long timeoutMillis, long revokeAfterKilledDelayMillis, int importanceToResetTimer,
            int importanceToKeepSessionAlive) {
            long timeoutMillis, long revokeAfterKilledDelayMillis) {
        mContext.enforceCallingOrSelfPermission(
                Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
                "Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
@@ -396,8 +395,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
        final long token = Binder.clearCallingIdentity();
        try {
            getOneTimePermissionUserManager(userId).startPackageOneTimeSession(packageName,
                    timeoutMillis, revokeAfterKilledDelayMillis, importanceToResetTimer,
                    importanceToKeepSessionAlive);
                    timeoutMillis, revokeAfterKilledDelayMillis);
        } finally {
            Binder.restoreCallingIdentity(token);
        }