1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.pm.permission; 18 19 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; 20 21 import android.annotation.NonNull; 22 import android.app.ActivityManager; 23 import android.app.AlarmManager; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.os.Handler; 30 import android.permission.PermissionControllerManager; 31 import android.provider.DeviceConfig; 32 import android.util.Log; 33 import android.util.SparseArray; 34 35 import com.android.internal.annotations.GuardedBy; 36 37 /** 38 * Class that handles one-time permissions for a user 39 */ 40 public class OneTimePermissionUserManager { 41 42 private static final String LOG_TAG = OneTimePermissionUserManager.class.getSimpleName(); 43 44 private static final boolean DEBUG = false; 45 private static final long DEFAULT_KILLED_DELAY_MILLIS = 5000; 46 public static final String PROPERTY_KILLED_DELAY_CONFIG_KEY = 47 "one_time_permissions_killed_delay_millis"; 48 49 private final @NonNull Context mContext; 50 private final @NonNull ActivityManager mActivityManager; 51 private final @NonNull AlarmManager mAlarmManager; 52 private final @NonNull PermissionControllerManager mPermissionControllerManager; 53 54 private final Object mLock = new Object(); 55 56 private final BroadcastReceiver mUninstallListener = new BroadcastReceiver() { 57 @Override 58 public void onReceive(Context context, Intent intent) { 59 if (Intent.ACTION_UID_REMOVED.equals(intent.getAction())) { 60 int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); 61 PackageInactivityListener listener = mListeners.get(uid); 62 if (listener != null) { 63 if (DEBUG) { 64 Log.d(LOG_TAG, "Removing the inactivity listener for " + uid); 65 } 66 listener.cancel(); 67 mListeners.remove(uid); 68 } 69 } 70 } 71 }; 72 73 /** Maps the uid to the PackageInactivityListener */ 74 @GuardedBy("mLock") 75 private final SparseArray<PackageInactivityListener> mListeners = new SparseArray<>(); 76 private final Handler mHandler; 77 OneTimePermissionUserManager(@onNull Context context)78 OneTimePermissionUserManager(@NonNull Context context) { 79 mContext = context; 80 mActivityManager = context.getSystemService(ActivityManager.class); 81 mAlarmManager = context.getSystemService(AlarmManager.class); 82 mPermissionControllerManager = context.getSystemService(PermissionControllerManager.class); 83 mHandler = context.getMainThreadHandler(); 84 } 85 86 /** 87 * Starts a one-time permission session for a given package. A one-time permission session is 88 * ended if app becomes inactive. Inactivity is defined as the package's uid importance level 89 * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid 90 * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start 91 * until going > importanceToResetTimer. 92 * <p> 93 * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive 94 * then the session is extended until either the importance goes above 95 * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which 96 * will continue the session and reset the timer. 97 * </p> 98 * <p> 99 * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. 100 * </p> 101 * <p> 102 * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout 103 * is invoked. 104 * </p> 105 * <p> 106 * Note that if there is currently an active session for a package a new one isn't created and 107 * the existing one isn't changed. 108 * </p> 109 * @param packageName The package to start a one-time permission session for 110 * @param timeoutMillis Number of milliseconds for an app to be in an inactive state 111 * @param importanceToResetTimer The least important level to uid must be to reset the timer 112 * @param importanceToKeepSessionAlive The least important level the uid must be to keep the 113 * session alive 114 * 115 * @hide 116 */ startPackageOneTimeSession(@onNull String packageName, long timeoutMillis, int importanceToResetTimer, int importanceToKeepSessionAlive)117 void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, 118 int importanceToResetTimer, int importanceToKeepSessionAlive) { 119 int uid; 120 try { 121 uid = mContext.getPackageManager().getPackageUid(packageName, 0); 122 } catch (PackageManager.NameNotFoundException e) { 123 Log.e(LOG_TAG, "Unknown package name " + packageName, e); 124 return; 125 } 126 127 synchronized (mLock) { 128 PackageInactivityListener listener = mListeners.get(uid); 129 if (listener == null) { 130 listener = new PackageInactivityListener(uid, packageName, timeoutMillis, 131 importanceToResetTimer, importanceToKeepSessionAlive); 132 mListeners.put(uid, listener); 133 } 134 } 135 } 136 137 /** 138 * Stops the one-time permission session for the package. The callback to the end of session is 139 * not invoked. If there is no one-time session for the package then nothing happens. 140 * 141 * @param packageName Package to stop the one-time permission session for 142 */ stopPackageOneTimeSession(@onNull String packageName)143 void stopPackageOneTimeSession(@NonNull String packageName) { 144 int uid; 145 try { 146 uid = mContext.getPackageManager().getPackageUid(packageName, 0); 147 } catch (PackageManager.NameNotFoundException e) { 148 Log.e(LOG_TAG, "Unknown package name " + packageName, e); 149 return; 150 } 151 152 synchronized (mLock) { 153 PackageInactivityListener listener = mListeners.get(uid); 154 if (listener != null) { 155 mListeners.remove(uid); 156 listener.cancel(); 157 } 158 } 159 } 160 161 /** 162 * The delay to wait before revoking on the event an app is terminated. Recommended to be long 163 * enough so that apps don't lose permission on an immediate restart 164 */ getKilledDelayMillis()165 private static long getKilledDelayMillis() { 166 return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, 167 PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS); 168 } 169 170 /** 171 * Register to listen for Uids being uninstalled. This must be done outside of the 172 * PermissionManagerService lock. 173 */ registerUninstallListener()174 void registerUninstallListener() { 175 mContext.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); 176 } 177 178 /** 179 * A class which watches a package for inactivity and notifies the permission controller when 180 * the package becomes inactive 181 */ 182 private class PackageInactivityListener implements AlarmManager.OnAlarmListener { 183 184 private static final long TIMER_INACTIVE = -1; 185 186 private final int mUid; 187 private final @NonNull String mPackageName; 188 private final long mTimeout; 189 private final int mImportanceToResetTimer; 190 private final int mImportanceToKeepSessionAlive; 191 192 private boolean mIsAlarmSet; 193 private boolean mIsFinished; 194 195 private long mTimerStart = TIMER_INACTIVE; 196 197 private final ActivityManager.OnUidImportanceListener mStartTimerListener; 198 private final ActivityManager.OnUidImportanceListener mSessionKillableListener; 199 private final ActivityManager.OnUidImportanceListener mGoneListener; 200 201 private final Object mInnerLock = new Object(); 202 private final Object mToken = new Object(); 203 PackageInactivityListener(int uid, @NonNull String packageName, long timeout, int importanceToResetTimer, int importanceToKeepSessionAlive)204 private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, 205 int importanceToResetTimer, int importanceToKeepSessionAlive) { 206 207 Log.i(LOG_TAG, 208 "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout 209 + " importanceToResetTimer=" + importanceToResetTimer 210 + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); 211 212 mUid = uid; 213 mPackageName = packageName; 214 mTimeout = timeout; 215 mImportanceToResetTimer = importanceToResetTimer; 216 mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; 217 218 mStartTimerListener = 219 (changingUid, importance) -> onImportanceChanged(changingUid, importance); 220 mSessionKillableListener = 221 (changingUid, importance) -> onImportanceChanged(changingUid, importance); 222 mGoneListener = 223 (changingUid, importance) -> onImportanceChanged(changingUid, importance); 224 225 mActivityManager.addOnUidImportanceListener(mStartTimerListener, 226 importanceToResetTimer); 227 mActivityManager.addOnUidImportanceListener(mSessionKillableListener, 228 importanceToKeepSessionAlive); 229 mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); 230 231 onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); 232 } 233 onImportanceChanged(int uid, int importance)234 private void onImportanceChanged(int uid, int importance) { 235 if (uid != mUid) { 236 return; 237 } 238 239 Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." 240 + " importance=" + importance); 241 synchronized (mInnerLock) { 242 // Remove any pending inactivity callback 243 mHandler.removeCallbacksAndMessages(mToken); 244 245 if (importance > IMPORTANCE_CACHED) { 246 // Delay revocation in case app is restarting 247 mHandler.postDelayed(() -> { 248 int imp = mActivityManager.getUidImportance(mUid); 249 if (imp > IMPORTANCE_CACHED) { 250 onPackageInactiveLocked(); 251 } else { 252 if (DEBUG) { 253 Log.d(LOG_TAG, "No longer gone after delayed revocation. " 254 + "Rechecking for " + mPackageName + " (" + mUid + ")."); 255 } 256 onImportanceChanged(mUid, imp); 257 } 258 }, mToken, getKilledDelayMillis()); 259 return; 260 } 261 if (importance > mImportanceToResetTimer) { 262 if (mTimerStart == TIMER_INACTIVE) { 263 if (DEBUG) { 264 Log.d(LOG_TAG, "Start the timer for " 265 + mPackageName + " (" + mUid + ")."); 266 } 267 mTimerStart = System.currentTimeMillis(); 268 } 269 } else { 270 mTimerStart = TIMER_INACTIVE; 271 } 272 if (importance > mImportanceToKeepSessionAlive) { 273 setAlarmLocked(); 274 } else { 275 cancelAlarmLocked(); 276 } 277 } 278 } 279 280 /** 281 * Stop watching the package for inactivity 282 */ cancel()283 private void cancel() { 284 synchronized (mInnerLock) { 285 mIsFinished = true; 286 cancelAlarmLocked(); 287 mActivityManager.removeOnUidImportanceListener(mStartTimerListener); 288 mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); 289 mActivityManager.removeOnUidImportanceListener(mGoneListener); 290 } 291 } 292 293 /** 294 * Set the alarm which will callback when the package is inactive 295 */ 296 @GuardedBy("mInnerLock") setAlarmLocked()297 private void setAlarmLocked() { 298 if (mIsAlarmSet) { 299 return; 300 } 301 302 if (DEBUG) { 303 Log.d(LOG_TAG, "Scheduling alarm for " + mPackageName + " (" + mUid + ")."); 304 } 305 long revokeTime = mTimerStart + mTimeout; 306 if (revokeTime > System.currentTimeMillis()) { 307 mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, revokeTime, LOG_TAG, this, 308 mHandler); 309 mIsAlarmSet = true; 310 } else { 311 mIsAlarmSet = true; 312 onAlarm(); 313 } 314 } 315 316 /** 317 * Cancel the alarm 318 */ 319 @GuardedBy("mInnerLock") cancelAlarmLocked()320 private void cancelAlarmLocked() { 321 if (mIsAlarmSet) { 322 if (DEBUG) { 323 Log.d(LOG_TAG, "Canceling alarm for " + mPackageName + " (" + mUid + ")."); 324 } 325 mAlarmManager.cancel(this); 326 mIsAlarmSet = false; 327 } 328 } 329 330 /** 331 * Called when the package is considered inactive. This is the end of the session 332 */ 333 @GuardedBy("mInnerLock") onPackageInactiveLocked()334 private void onPackageInactiveLocked() { 335 if (mIsFinished) { 336 return; 337 } 338 if (DEBUG) { 339 Log.d(LOG_TAG, "onPackageInactiveLocked stack trace for " 340 + mPackageName + " (" + mUid + ").", new RuntimeException()); 341 } 342 mIsFinished = true; 343 cancelAlarmLocked(); 344 mHandler.post( 345 () -> { 346 Log.i(LOG_TAG, "One time session expired for " 347 + mPackageName + " (" + mUid + ")."); 348 349 mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( 350 mPackageName); 351 }); 352 mActivityManager.removeOnUidImportanceListener(mStartTimerListener); 353 mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); 354 mActivityManager.removeOnUidImportanceListener(mGoneListener); 355 synchronized (mLock) { 356 mListeners.remove(mUid); 357 } 358 } 359 360 @Override onAlarm()361 public void onAlarm() { 362 if (DEBUG) { 363 Log.d(LOG_TAG, "Alarm received for " + mPackageName + " (" + mUid + ")."); 364 } 365 synchronized (mInnerLock) { 366 if (!mIsAlarmSet) { 367 return; 368 } 369 mIsAlarmSet = false; 370 onPackageInactiveLocked(); 371 } 372 } 373 } 374 } 375