• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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