Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 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; |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 24 | import android.content.BroadcastReceiver; |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 25 | import android.content.Context; |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 26 | import android.content.Intent; |
| 27 | import android.content.IntentFilter; |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 28 | import android.content.pm.PackageManager; |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 29 | import android.os.Handler; |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 30 | import android.permission.PermissionControllerManager; |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 31 | import android.provider.DeviceConfig; |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 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 | |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 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"; |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 48 | |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 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 | |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 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 | |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 73 | /** Maps the uid to the PackageInactivityListener */ |
| 74 | @GuardedBy("mLock") |
| 75 | private final SparseArray<PackageInactivityListener> mListeners = new SparseArray<>(); |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 76 | private final Handler mHandler; |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 77 | |
| 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); |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 83 | mHandler = context.getMainThreadHandler(); |
| 84 | |
| 85 | // Listen for tracked uid being uninstalled |
| 86 | context.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED)); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Starts a one-time permission session for a given package. A one-time permission session is |
| 91 | * ended if app becomes inactive. Inactivity is defined as the package's uid importance level |
| 92 | * staying > importanceToResetTimer for timeoutMillis milliseconds. If the package's uid |
| 93 | * importance level goes <= importanceToResetTimer then the timer is reset and doesn't start |
| 94 | * until going > importanceToResetTimer. |
| 95 | * <p> |
| 96 | * When this timeoutMillis is reached if the importance level is <= importanceToKeepSessionAlive |
| 97 | * then the session is extended until either the importance goes above |
| 98 | * importanceToKeepSessionAlive which will end the session or <= importanceToResetTimer which |
| 99 | * will continue the session and reset the timer. |
| 100 | * </p> |
| 101 | * <p> |
| 102 | * Importance levels are defined in {@link android.app.ActivityManager.RunningAppProcessInfo}. |
| 103 | * </p> |
| 104 | * <p> |
| 105 | * Once the session ends PermissionControllerService#onNotifyOneTimePermissionSessionTimeout |
| 106 | * is invoked. |
| 107 | * </p> |
| 108 | * <p> |
| 109 | * Note that if there is currently an active session for a package a new one isn't created and |
| 110 | * the existing one isn't changed. |
| 111 | * </p> |
| 112 | * @param packageName The package to start a one-time permission session for |
| 113 | * @param timeoutMillis Number of milliseconds for an app to be in an inactive state |
| 114 | * @param importanceToResetTimer The least important level to uid must be to reset the timer |
| 115 | * @param importanceToKeepSessionAlive The least important level the uid must be to keep the |
| 116 | * session alive |
| 117 | * |
| 118 | * @hide |
| 119 | */ |
| 120 | void startPackageOneTimeSession(@NonNull String packageName, long timeoutMillis, |
| 121 | int importanceToResetTimer, int importanceToKeepSessionAlive) { |
| 122 | int uid; |
| 123 | try { |
| 124 | uid = mContext.getPackageManager().getPackageUid(packageName, 0); |
| 125 | } catch (PackageManager.NameNotFoundException e) { |
| 126 | Log.e(LOG_TAG, "Unknown package name " + packageName, e); |
| 127 | return; |
| 128 | } |
| 129 | |
| 130 | synchronized (mLock) { |
| 131 | PackageInactivityListener listener = mListeners.get(uid); |
| 132 | if (listener == null) { |
| 133 | listener = new PackageInactivityListener(uid, packageName, timeoutMillis, |
| 134 | importanceToResetTimer, importanceToKeepSessionAlive); |
| 135 | mListeners.put(uid, listener); |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Stops the one-time permission session for the package. The callback to the end of session is |
| 142 | * not invoked. If there is no one-time session for the package then nothing happens. |
| 143 | * |
| 144 | * @param packageName Package to stop the one-time permission session for |
| 145 | */ |
| 146 | void stopPackageOneTimeSession(@NonNull String packageName) { |
| 147 | int uid; |
| 148 | try { |
| 149 | uid = mContext.getPackageManager().getPackageUid(packageName, 0); |
| 150 | } catch (PackageManager.NameNotFoundException e) { |
| 151 | Log.e(LOG_TAG, "Unknown package name " + packageName, e); |
| 152 | return; |
| 153 | } |
| 154 | |
| 155 | synchronized (mLock) { |
| 156 | PackageInactivityListener listener = mListeners.get(uid); |
| 157 | if (listener != null) { |
| 158 | mListeners.remove(uid); |
| 159 | listener.cancel(); |
| 160 | } |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | /** |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 165 | * The delay to wait before revoking on the event an app is terminated. Recommended to be long |
| 166 | * enough so that apps don't lose permission on an immediate restart |
| 167 | */ |
| 168 | private static long getKilledDelayMillis() { |
| 169 | return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS, |
| 170 | PROPERTY_KILLED_DELAY_CONFIG_KEY, DEFAULT_KILLED_DELAY_MILLIS); |
| 171 | } |
| 172 | |
| 173 | /** |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 174 | * A class which watches a package for inactivity and notifies the permission controller when |
| 175 | * the package becomes inactive |
| 176 | */ |
| 177 | private class PackageInactivityListener implements AlarmManager.OnAlarmListener { |
| 178 | |
| 179 | private static final long TIMER_INACTIVE = -1; |
| 180 | |
| 181 | private final int mUid; |
| 182 | private final @NonNull String mPackageName; |
| 183 | private final long mTimeout; |
| 184 | private final int mImportanceToResetTimer; |
| 185 | private final int mImportanceToKeepSessionAlive; |
| 186 | |
| 187 | private boolean mIsAlarmSet; |
| 188 | private boolean mIsFinished; |
| 189 | |
| 190 | private long mTimerStart = TIMER_INACTIVE; |
| 191 | |
| 192 | private final ActivityManager.OnUidImportanceListener mStartTimerListener; |
| 193 | private final ActivityManager.OnUidImportanceListener mSessionKillableListener; |
| 194 | private final ActivityManager.OnUidImportanceListener mGoneListener; |
| 195 | |
| 196 | private final Object mInnerLock = new Object(); |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 197 | private final Object mToken = new Object(); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 198 | |
| 199 | private PackageInactivityListener(int uid, @NonNull String packageName, long timeout, |
| 200 | int importanceToResetTimer, int importanceToKeepSessionAlive) { |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 201 | |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 202 | Log.i(LOG_TAG, |
| 203 | "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout |
| 204 | + " importanceToResetTimer=" + importanceToResetTimer |
| 205 | + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive); |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 206 | |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 207 | mUid = uid; |
| 208 | mPackageName = packageName; |
| 209 | mTimeout = timeout; |
| 210 | mImportanceToResetTimer = importanceToResetTimer; |
| 211 | mImportanceToKeepSessionAlive = importanceToKeepSessionAlive; |
| 212 | |
| 213 | mStartTimerListener = |
| 214 | (changingUid, importance) -> onImportanceChanged(changingUid, importance); |
| 215 | mSessionKillableListener = |
| 216 | (changingUid, importance) -> onImportanceChanged(changingUid, importance); |
| 217 | mGoneListener = |
| 218 | (changingUid, importance) -> onImportanceChanged(changingUid, importance); |
| 219 | |
| 220 | mActivityManager.addOnUidImportanceListener(mStartTimerListener, |
| 221 | importanceToResetTimer); |
| 222 | mActivityManager.addOnUidImportanceListener(mSessionKillableListener, |
| 223 | importanceToKeepSessionAlive); |
| 224 | mActivityManager.addOnUidImportanceListener(mGoneListener, IMPORTANCE_CACHED); |
| 225 | |
| 226 | onImportanceChanged(mUid, mActivityManager.getPackageImportance(packageName)); |
| 227 | } |
| 228 | |
| 229 | private void onImportanceChanged(int uid, int importance) { |
| 230 | if (uid != mUid) { |
| 231 | return; |
| 232 | } |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 233 | |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 234 | Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")." |
| 235 | + " importance=" + importance); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 236 | synchronized (mInnerLock) { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 237 | // Remove any pending inactivity callback |
| 238 | mHandler.removeCallbacksAndMessages(mToken); |
| 239 | |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 240 | if (importance > IMPORTANCE_CACHED) { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 241 | // Delay revocation in case app is restarting |
| 242 | mHandler.postDelayed(() -> { |
| 243 | int imp = mActivityManager.getUidImportance(mUid); |
| 244 | if (imp > IMPORTANCE_CACHED) { |
| 245 | onPackageInactiveLocked(); |
| 246 | } else { |
| 247 | if (DEBUG) { |
| 248 | Log.d(LOG_TAG, "No longer gone after delayed revocation. " |
| 249 | + "Rechecking for " + mPackageName + " (" + mUid + ")."); |
| 250 | } |
| 251 | onImportanceChanged(mUid, imp); |
| 252 | } |
| 253 | }, mToken, getKilledDelayMillis()); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 254 | return; |
| 255 | } |
| 256 | if (importance > mImportanceToResetTimer) { |
| 257 | if (mTimerStart == TIMER_INACTIVE) { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 258 | if (DEBUG) { |
| 259 | Log.d(LOG_TAG, "Start the timer for " |
| 260 | + mPackageName + " (" + mUid + ")."); |
| 261 | } |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 262 | mTimerStart = System.currentTimeMillis(); |
| 263 | } |
| 264 | } else { |
| 265 | mTimerStart = TIMER_INACTIVE; |
| 266 | } |
| 267 | if (importance > mImportanceToKeepSessionAlive) { |
| 268 | setAlarmLocked(); |
| 269 | } else { |
| 270 | cancelAlarmLocked(); |
| 271 | } |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | /** |
| 276 | * Stop watching the package for inactivity |
| 277 | */ |
| 278 | private void cancel() { |
| 279 | synchronized (mInnerLock) { |
| 280 | mIsFinished = true; |
| 281 | cancelAlarmLocked(); |
| 282 | mActivityManager.removeOnUidImportanceListener(mStartTimerListener); |
| 283 | mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); |
| 284 | mActivityManager.removeOnUidImportanceListener(mGoneListener); |
| 285 | } |
| 286 | } |
| 287 | |
| 288 | /** |
| 289 | * Set the alarm which will callback when the package is inactive |
| 290 | */ |
| 291 | @GuardedBy("mInnerLock") |
| 292 | private void setAlarmLocked() { |
| 293 | if (mIsAlarmSet) { |
| 294 | return; |
| 295 | } |
| 296 | |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 297 | if (DEBUG) { |
| 298 | Log.d(LOG_TAG, "Scheduling alarm for " + mPackageName + " (" + mUid + ")."); |
| 299 | } |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 300 | long revokeTime = mTimerStart + mTimeout; |
| 301 | if (revokeTime > System.currentTimeMillis()) { |
| 302 | mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, revokeTime, LOG_TAG, this, |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 303 | mHandler); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 304 | mIsAlarmSet = true; |
| 305 | } else { |
| 306 | mIsAlarmSet = true; |
| 307 | onAlarm(); |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | /** |
| 312 | * Cancel the alarm |
| 313 | */ |
| 314 | @GuardedBy("mInnerLock") |
| 315 | private void cancelAlarmLocked() { |
| 316 | if (mIsAlarmSet) { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 317 | if (DEBUG) { |
| 318 | Log.d(LOG_TAG, "Canceling alarm for " + mPackageName + " (" + mUid + ")."); |
| 319 | } |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 320 | mAlarmManager.cancel(this); |
| 321 | mIsAlarmSet = false; |
| 322 | } |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * Called when the package is considered inactive. This is the end of the session |
| 327 | */ |
| 328 | @GuardedBy("mInnerLock") |
| 329 | private void onPackageInactiveLocked() { |
| 330 | if (mIsFinished) { |
| 331 | return; |
| 332 | } |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 333 | if (DEBUG) { |
| 334 | Log.d(LOG_TAG, "onPackageInactiveLocked stack trace for " |
| 335 | + mPackageName + " (" + mUid + ").", new RuntimeException()); |
| 336 | } |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 337 | mIsFinished = true; |
| 338 | cancelAlarmLocked(); |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 339 | mHandler.post( |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 340 | () -> { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 341 | Log.i(LOG_TAG, "One time session expired for " |
| 342 | + mPackageName + " (" + mUid + ")."); |
Evan Severson | 322de3a | 2020-02-11 11:28:31 -0800 | [diff] [blame] | 343 | |
| 344 | mPermissionControllerManager.notifyOneTimePermissionSessionTimeout( |
| 345 | mPackageName); |
| 346 | }); |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 347 | mActivityManager.removeOnUidImportanceListener(mStartTimerListener); |
| 348 | mActivityManager.removeOnUidImportanceListener(mSessionKillableListener); |
| 349 | mActivityManager.removeOnUidImportanceListener(mGoneListener); |
| 350 | synchronized (mLock) { |
| 351 | mListeners.remove(mUid); |
| 352 | } |
| 353 | } |
| 354 | |
| 355 | @Override |
| 356 | public void onAlarm() { |
Evan Severson | 3535e81 | 2020-04-09 22:12:37 -0700 | [diff] [blame] | 357 | if (DEBUG) { |
| 358 | Log.d(LOG_TAG, "Alarm received for " + mPackageName + " (" + mUid + ")."); |
| 359 | } |
Evan Severson | b252d8b | 2019-11-20 08:41:33 -0800 | [diff] [blame] | 360 | synchronized (mInnerLock) { |
| 361 | if (!mIsAlarmSet) { |
| 362 | return; |
| 363 | } |
| 364 | mIsAlarmSet = false; |
| 365 | onPackageInactiveLocked(); |
| 366 | } |
| 367 | } |
| 368 | } |
| 369 | } |