blob: 1c4568095ce3467004104f7bcbcabbefce8e2879 [file] [log] [blame]
Evan Seversonb252d8b2019-11-20 08:41:33 -08001/*
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
17package com.android.server.pm.permission;
18
19import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
20
21import android.annotation.NonNull;
22import android.app.ActivityManager;
23import android.app.AlarmManager;
Evan Severson3535e812020-04-09 22:12:37 -070024import android.content.BroadcastReceiver;
Evan Seversonb252d8b2019-11-20 08:41:33 -080025import android.content.Context;
Evan Severson3535e812020-04-09 22:12:37 -070026import android.content.Intent;
27import android.content.IntentFilter;
Evan Seversonb252d8b2019-11-20 08:41:33 -080028import android.content.pm.PackageManager;
Evan Severson3535e812020-04-09 22:12:37 -070029import android.os.Handler;
Evan Seversonb252d8b2019-11-20 08:41:33 -080030import android.permission.PermissionControllerManager;
Evan Severson3535e812020-04-09 22:12:37 -070031import android.provider.DeviceConfig;
Evan Seversonb252d8b2019-11-20 08:41:33 -080032import android.util.Log;
33import android.util.SparseArray;
34
35import com.android.internal.annotations.GuardedBy;
36
37/**
38 * Class that handles one-time permissions for a user
39 */
40public class OneTimePermissionUserManager {
41
42 private static final String LOG_TAG = OneTimePermissionUserManager.class.getSimpleName();
43
Evan Severson3535e812020-04-09 22:12:37 -070044 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 Severson322de3a2020-02-11 11:28:31 -080048
Evan Seversonb252d8b2019-11-20 08:41:33 -080049 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 Severson3535e812020-04-09 22:12:37 -070056 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 Seversonb252d8b2019-11-20 08:41:33 -080073 /** Maps the uid to the PackageInactivityListener */
74 @GuardedBy("mLock")
75 private final SparseArray<PackageInactivityListener> mListeners = new SparseArray<>();
Evan Severson3535e812020-04-09 22:12:37 -070076 private final Handler mHandler;
Evan Seversonb252d8b2019-11-20 08:41:33 -080077
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 Severson3535e812020-04-09 22:12:37 -070083 mHandler = context.getMainThreadHandler();
84
85 // Listen for tracked uid being uninstalled
86 context.registerReceiver(mUninstallListener, new IntentFilter(Intent.ACTION_UID_REMOVED));
Evan Seversonb252d8b2019-11-20 08:41:33 -080087 }
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 Severson3535e812020-04-09 22:12:37 -0700165 * 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 Seversonb252d8b2019-11-20 08:41:33 -0800174 * 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 Severson3535e812020-04-09 22:12:37 -0700197 private final Object mToken = new Object();
Evan Seversonb252d8b2019-11-20 08:41:33 -0800198
199 private PackageInactivityListener(int uid, @NonNull String packageName, long timeout,
200 int importanceToResetTimer, int importanceToKeepSessionAlive) {
Evan Severson322de3a2020-02-11 11:28:31 -0800201
Evan Severson3535e812020-04-09 22:12:37 -0700202 Log.i(LOG_TAG,
203 "Start tracking " + packageName + ". uid=" + uid + " timeout=" + timeout
204 + " importanceToResetTimer=" + importanceToResetTimer
205 + " importanceToKeepSessionAlive=" + importanceToKeepSessionAlive);
Evan Severson322de3a2020-02-11 11:28:31 -0800206
Evan Seversonb252d8b2019-11-20 08:41:33 -0800207 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 Severson322de3a2020-02-11 11:28:31 -0800233
Evan Severson3535e812020-04-09 22:12:37 -0700234 Log.v(LOG_TAG, "Importance changed for " + mPackageName + " (" + mUid + ")."
235 + " importance=" + importance);
Evan Seversonb252d8b2019-11-20 08:41:33 -0800236 synchronized (mInnerLock) {
Evan Severson3535e812020-04-09 22:12:37 -0700237 // Remove any pending inactivity callback
238 mHandler.removeCallbacksAndMessages(mToken);
239
Evan Seversonb252d8b2019-11-20 08:41:33 -0800240 if (importance > IMPORTANCE_CACHED) {
Evan Severson3535e812020-04-09 22:12:37 -0700241 // 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 Seversonb252d8b2019-11-20 08:41:33 -0800254 return;
255 }
256 if (importance > mImportanceToResetTimer) {
257 if (mTimerStart == TIMER_INACTIVE) {
Evan Severson3535e812020-04-09 22:12:37 -0700258 if (DEBUG) {
259 Log.d(LOG_TAG, "Start the timer for "
260 + mPackageName + " (" + mUid + ").");
261 }
Evan Seversonb252d8b2019-11-20 08:41:33 -0800262 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 Severson3535e812020-04-09 22:12:37 -0700297 if (DEBUG) {
298 Log.d(LOG_TAG, "Scheduling alarm for " + mPackageName + " (" + mUid + ").");
299 }
Evan Seversonb252d8b2019-11-20 08:41:33 -0800300 long revokeTime = mTimerStart + mTimeout;
301 if (revokeTime > System.currentTimeMillis()) {
302 mAlarmManager.setExact(AlarmManager.RTC_WAKEUP, revokeTime, LOG_TAG, this,
Evan Severson3535e812020-04-09 22:12:37 -0700303 mHandler);
Evan Seversonb252d8b2019-11-20 08:41:33 -0800304 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 Severson3535e812020-04-09 22:12:37 -0700317 if (DEBUG) {
318 Log.d(LOG_TAG, "Canceling alarm for " + mPackageName + " (" + mUid + ").");
319 }
Evan Seversonb252d8b2019-11-20 08:41:33 -0800320 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 Severson3535e812020-04-09 22:12:37 -0700333 if (DEBUG) {
334 Log.d(LOG_TAG, "onPackageInactiveLocked stack trace for "
335 + mPackageName + " (" + mUid + ").", new RuntimeException());
336 }
Evan Seversonb252d8b2019-11-20 08:41:33 -0800337 mIsFinished = true;
338 cancelAlarmLocked();
Evan Severson3535e812020-04-09 22:12:37 -0700339 mHandler.post(
Evan Severson322de3a2020-02-11 11:28:31 -0800340 () -> {
Evan Severson3535e812020-04-09 22:12:37 -0700341 Log.i(LOG_TAG, "One time session expired for "
342 + mPackageName + " (" + mUid + ").");
Evan Severson322de3a2020-02-11 11:28:31 -0800343
344 mPermissionControllerManager.notifyOneTimePermissionSessionTimeout(
345 mPackageName);
346 });
Evan Seversonb252d8b2019-11-20 08:41:33 -0800347 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 Severson3535e812020-04-09 22:12:37 -0700357 if (DEBUG) {
358 Log.d(LOG_TAG, "Alarm received for " + mPackageName + " (" + mUid + ").");
359 }
Evan Seversonb252d8b2019-11-20 08:41:33 -0800360 synchronized (mInnerLock) {
361 if (!mIsAlarmSet) {
362 return;
363 }
364 mIsAlarmSet = false;
365 onPackageInactiveLocked();
366 }
367 }
368 }
369}