Make one-time permission sessions continue over service restarts

When a sticky service crashes and is restarted by the system it should
retain its permission even if granted one-time.
Also make one time permission manager aware of package uninstalls.

Test: atest OneTimePermissionTest
Bug: 151405396
Fixes: 153701894
Change-Id: I7c440394aecf4308ecfe904ff56ffac94f0150ef
This commit is contained in:
Evan Severson
2020-04-09 22:12:37 -07:00
parent 55815db140
commit 3535e8156f

View File

@@ -21,9 +21,14 @@ import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHE
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.AlarmManager;
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.permission.PermissionControllerManager;
import android.provider.DeviceConfig;
import android.util.Log;
import android.util.SparseArray;
@@ -36,7 +41,10 @@ public class OneTimePermissionUserManager {
private static final String LOG_TAG = OneTimePermissionUserManager.class.getSimpleName();
private static final boolean DEBUG = true;
private static final boolean DEBUG = false;
private static final long DEFAULT_KILLED_DELAY_MILLIS = 5000;
public static final String PROPERTY_KILLED_DELAY_CONFIG_KEY =
"one_time_permissions_killed_delay_millis";
private final @NonNull Context mContext;
private final @NonNull ActivityManager mActivityManager;
@@ -45,15 +53,37 @@ public class OneTimePermissionUserManager {
private final Object mLock = new Object();
private final BroadcastReceiver mUninstallListener = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_UID_REMOVED.equals(intent.getAction())) {
int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
PackageInactivityListener listener = mListeners.get(uid);
if (listener != null) {
if (DEBUG) {
Log.d(LOG_TAG, "Removing the inactivity listener for " + uid);
}
listener.cancel();
mListeners.remove(uid);
}
}
}
};
/** Maps the uid to the PackageInactivityListener */
@GuardedBy("mLock")
private final SparseArray<PackageInactivityListener> mListeners = new SparseArray<>();
private final Handler mHandler;
OneTimePermissionUserManager(@NonNull Context context) {
mContext = context;
mActivityManager = context.getSystemService(ActivityManager.class);
mAlarmManager = context.getSystemService(AlarmManager.class);
mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class);
mHandler = context.getMainThreadHandler();
// Listen for tracked uid being uninstalled
context.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED));
}
/**
@@ -131,6 +161,15 @@ public class OneTimePermissionUserManager {
}
}
/**
* The delay to wait before revoking on the event an app is terminated. Recommended to be long
* enough so that apps don't lose permission on an immediate restart
*/
private static long getKilledDelayMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS);
}
/**
* A class which watches a package for inactivity and notifies the permission controller when
* the package becomes inactive
@@ -155,16 +194,15 @@ public class OneTimePermissionUserManager {
private final ActivityManager.OnUidImportanceListener mGoneListener;
private final Object mInnerLock = new Object();
private final Object mToken = new Object();
private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
int importanceToResetTimer, int importanceToKeepSessionAlive) {
if (DEBUG) {
Log.d(LOG_TAG,
"Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
+ " importanceToResetTimer=" + importanceToResetTimer
+ " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
}
Log.i(LOG_TAG,
"Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
+ " importanceToResetTimer=" + importanceToResetTimer
+ " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
mUid = uid;
mPackageName = packageName;
@@ -193,18 +231,34 @@ public class OneTimePermissionUserManager {
return;
}
if (DEBUG) {
Log.d(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
+ " importance=" + importance);
}
Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
+ " importance=" + importance);
synchronized (mInnerLock) {
// Remove any pending inactivity callback
mHandler.removeCallbacksAndMessages(mToken);
if (importance > IMPORTANCE_CACHED) {
onPackageInactiveLocked();
// Delay revocation in case app is restarting
mHandler.postDelayed(() -> {
int imp = mActivityManager.getUidImportance(mUid);
if (imp > IMPORTANCE_CACHED) {
onPackageInactiveLocked();
} else {
if (DEBUG) {
Log.d(LOG_TAG, "No longer gone after delayed revocation. "
+ "Rechecking for " + mPackageName + " (" + mUid + ").");
}
onImportanceChanged(mUid, imp);
}
}, mToken, getKilledDelayMillis());
return;
}
if (importance > mImportanceToResetTimer) {
if (mTimerStart == TIMER_INACTIVE) {
if (DEBUG) {
Log.d(LOG_TAG, "Start the timer for "
+ mPackageName + " (" + mUid + ").");
}
mTimerStart = System.currentTimeMillis();
}
} else {
@@ -240,10 +294,13 @@ public class OneTimePermissionUserManager {
return;
}
if (DEBUG) {
Log.d(LOG_TAG, "Scheduling alarm for " + mPackageName + " (" + mUid + ").");
}
long revokeTime = mTimerStart + mTimeout;
if (revokeTime > System.currentTimeMillis()) {
mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, revokeTime, LOG_TAG, this,
mContext.getMainThreadHandler());
mHandler);
mIsAlarmSet = true;
} else {
mIsAlarmSet = true;
@@ -257,6 +314,9 @@ public class OneTimePermissionUserManager {
@GuardedBy("mInnerLock")
private void cancelAlarmLocked() {
if (mIsAlarmSet) {
if (DEBUG) {
Log.d(LOG_TAG, "Canceling alarm for " + mPackageName + " (" + mUid + ").");
}
mAlarmManager.cancel(this);
mIsAlarmSet = false;
}
@@ -270,14 +330,16 @@ public class OneTimePermissionUserManager {
if (mIsFinished) {
return;
}
if (DEBUG) {
Log.d(LOG_TAG, "onPackageInactiveLocked stack trace for "
+ mPackageName + " (" + mUid + ").", new RuntimeException());
}
mIsFinished = true;
cancelAlarmLocked();
mContext.getMainThreadHandler().post(
mHandler.post(
() -> {
if (DEBUG) {
Log.d(LOG_TAG, "One time session expired for "
+ mPackageName + " (" + mUid + ").");
}
Log.i(LOG_TAG, "One time session expired for "
+ mPackageName + " (" + mUid + ").");
mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
mPackageName);
@@ -292,6 +354,9 @@ public class OneTimePermissionUserManager {
@Override
public void onAlarm() {
if (DEBUG) {
Log.d(LOG_TAG, "Alarm received for " + mPackageName + " (" + mUid + ").");
}
synchronized (mInnerLock) {
if (!mIsAlarmSet) {
return;