1 /*
2  * Copyright (C) 2016 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 android.content.pm.permission;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.ServiceConnection;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.IBinder;
28 import android.os.Message;
29 import android.os.RemoteCallback;
30 import android.os.RemoteException;
31 import android.permissionpresenterservice.RuntimePermissionPresenterService;
32 import android.util.Log;
33 
34 import com.android.internal.annotations.GuardedBy;
35 import com.android.internal.os.SomeArgs;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 
41 /**
42  * This class provides information about runtime permissions for a specific
43  * app or all apps. This information is dedicated for presentation purposes
44  * and does not necessarily reflect the individual permissions requested/
45  * granted to an app as the platform may be grouping permissions to improve
46  * presentation and help the user make an informed choice. For example, all
47  * runtime permissions in the same permission group may be presented as a
48  * single permission in the UI.
49  *
50  * @hide
51  */
52 public final class RuntimePermissionPresenter {
53     private static final String TAG = "RuntimePermPresenter";
54 
55     /**
56      * The key for retrieving the result from the returned bundle.
57      *
58      * @hide
59      */
60     public static final String KEY_RESULT =
61             "android.content.pm.permission.RuntimePermissionPresenter.key.result";
62 
63     /**
64      * Listener for delivering a result.
65      */
66     public static abstract class OnResultCallback {
67         /**
68          * The result for {@link #getAppPermissions(String, OnResultCallback, Handler)}.
69          * @param permissions The permissions list.
70          */
onGetAppPermissions(@onNull List<RuntimePermissionPresentationInfo> permissions)71         public void onGetAppPermissions(@NonNull
72                 List<RuntimePermissionPresentationInfo> permissions) {
73             /* do nothing - stub */
74         }
75     }
76 
77     private static final Object sLock = new Object();
78 
79     @GuardedBy("sLock")
80     private static RuntimePermissionPresenter sInstance;
81 
82     private final RemoteService mRemoteService;
83 
84     /**
85      * Gets the singleton runtime permission presenter.
86      *
87      * @param context Context for accessing resources.
88      * @return The singleton instance.
89      */
getInstance(@onNull Context context)90     public static RuntimePermissionPresenter getInstance(@NonNull Context context) {
91         synchronized (sLock) {
92             if (sInstance == null) {
93                 sInstance = new RuntimePermissionPresenter(context.getApplicationContext());
94             }
95             return sInstance;
96         }
97     }
98 
RuntimePermissionPresenter(Context context)99     private RuntimePermissionPresenter(Context context) {
100         mRemoteService = new RemoteService(context);
101     }
102 
103     /**
104      * Gets the runtime permissions for an app.
105      *
106      * @param packageName The package for which to query.
107      * @param callback Callback to receive the result.
108      * @param handler Handler on which to invoke the callback.
109      */
getAppPermissions(@onNull String packageName, @NonNull OnResultCallback callback, @Nullable Handler handler)110     public void getAppPermissions(@NonNull String packageName,
111             @NonNull OnResultCallback callback, @Nullable Handler handler) {
112         SomeArgs args = SomeArgs.obtain();
113         args.arg1 = packageName;
114         args.arg2 = callback;
115         args.arg3 = handler;
116         Message message = mRemoteService.obtainMessage(
117                 RemoteService.MSG_GET_APP_PERMISSIONS, args);
118         mRemoteService.processMessage(message);
119     }
120 
121     /**
122      * Revoke the permission {@code permissionName} for app {@code packageName}
123      *
124      * @param packageName The package for which to revoke
125      * @param permissionName The permission to revoke
126      */
revokeRuntimePermission(String packageName, String permissionName)127     public void revokeRuntimePermission(String packageName, String permissionName) {
128         SomeArgs args = SomeArgs.obtain();
129         args.arg1 = packageName;
130         args.arg2 = permissionName;
131 
132         Message message = mRemoteService.obtainMessage(
133                 RemoteService.MSG_REVOKE_APP_PERMISSIONS, args);
134         mRemoteService.processMessage(message);
135     }
136 
137     private static final class RemoteService
138             extends Handler implements ServiceConnection {
139         private static final long UNBIND_TIMEOUT_MILLIS = 10000;
140 
141         public static final int MSG_GET_APP_PERMISSIONS = 1;
142         public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
143         public static final int MSG_UNBIND = 3;
144         public static final int MSG_REVOKE_APP_PERMISSIONS = 4;
145 
146         private final Object mLock = new Object();
147 
148         private final Context mContext;
149 
150         @GuardedBy("mLock")
151         private final List<Message> mPendingWork = new ArrayList<>();
152 
153         @GuardedBy("mLock")
154         private IRuntimePermissionPresenter mRemoteInstance;
155 
156         @GuardedBy("mLock")
157         private boolean mBound;
158 
RemoteService(Context context)159         public RemoteService(Context context) {
160             super(context.getMainLooper(), null, false);
161             mContext = context;
162         }
163 
processMessage(Message message)164         public void processMessage(Message message) {
165             synchronized (mLock) {
166                 if (!mBound) {
167                     Intent intent = new Intent(
168                             RuntimePermissionPresenterService.SERVICE_INTERFACE);
169                     intent.setPackage(mContext.getPackageManager()
170                             .getPermissionControllerPackageName());
171                     mBound = mContext.bindService(intent, this,
172                             Context.BIND_AUTO_CREATE);
173                 }
174                 mPendingWork.add(message);
175                 scheduleNextMessageIfNeededLocked();
176             }
177         }
178 
179         @Override
onServiceConnected(ComponentName name, IBinder service)180         public void onServiceConnected(ComponentName name, IBinder service) {
181             synchronized (mLock) {
182                 mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service);
183                 scheduleNextMessageIfNeededLocked();
184             }
185         }
186 
187         @Override
onServiceDisconnected(ComponentName name)188         public void onServiceDisconnected(ComponentName name) {
189             synchronized (mLock) {
190                 mRemoteInstance = null;
191             }
192         }
193 
194         @Override
handleMessage(Message msg)195         public void handleMessage(Message msg) {
196             switch (msg.what) {
197                 case MSG_GET_APP_PERMISSIONS: {
198                     SomeArgs args = (SomeArgs) msg.obj;
199                     final String packageName = (String) args.arg1;
200                     final OnResultCallback callback = (OnResultCallback) args.arg2;
201                     final Handler handler = (Handler) args.arg3;
202                     args.recycle();
203                     final IRuntimePermissionPresenter remoteInstance;
204                     synchronized (mLock) {
205                         remoteInstance = mRemoteInstance;
206                     }
207                     if (remoteInstance == null) {
208                         return;
209                     }
210                     try {
211                         remoteInstance.getAppPermissions(packageName,
212                                 new RemoteCallback(new RemoteCallback.OnResultListener() {
213                             @Override
214                             public void onResult(Bundle result) {
215                                 final List<RuntimePermissionPresentationInfo> reportedPermissions;
216                                 List<RuntimePermissionPresentationInfo> permissions = null;
217                                 if (result != null) {
218                                     permissions = result.getParcelableArrayList(KEY_RESULT);
219                                 }
220                                 if (permissions == null) {
221                                     permissions = Collections.emptyList();
222                                 }
223                                 reportedPermissions = permissions;
224                                 if (handler != null) {
225                                     handler.post(new Runnable() {
226                                         @Override
227                                         public void run() {
228                                             callback.onGetAppPermissions(reportedPermissions);
229                                         }
230                                     });
231                                 } else {
232                                     callback.onGetAppPermissions(reportedPermissions);
233                                 }
234                             }
235                         }, this));
236                     } catch (RemoteException re) {
237                         Log.e(TAG, "Error getting app permissions", re);
238                     }
239                     scheduleUnbind();
240                 } break;
241 
242                 case MSG_UNBIND: {
243                     synchronized (mLock) {
244                         if (mBound) {
245                             mContext.unbindService(this);
246                             mBound = false;
247                         }
248                         mRemoteInstance = null;
249                     }
250                 } break;
251 
252                 case MSG_REVOKE_APP_PERMISSIONS: {
253                     SomeArgs args = (SomeArgs) msg.obj;
254                     final String packageName = (String) args.arg1;
255                     final String permissionName = (String) args.arg2;
256                     args.recycle();
257                     final IRuntimePermissionPresenter remoteInstance;
258                     synchronized (mLock) {
259                         remoteInstance = mRemoteInstance;
260                     }
261                     if (remoteInstance == null) {
262                         return;
263                     }
264                     try {
265                         remoteInstance.revokeRuntimePermission(packageName, permissionName);
266                     } catch (RemoteException re) {
267                         Log.e(TAG, "Error getting app permissions", re);
268                     }
269                 } break;
270             }
271 
272             synchronized (mLock) {
273                 scheduleNextMessageIfNeededLocked();
274             }
275         }
276 
277         @GuardedBy("mLock")
scheduleNextMessageIfNeededLocked()278         private void scheduleNextMessageIfNeededLocked() {
279             if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
280                 Message nextMessage = mPendingWork.remove(0);
281                 sendMessage(nextMessage);
282             }
283         }
284 
scheduleUnbind()285         private void scheduleUnbind() {
286             removeMessages(MSG_UNBIND);
287             sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS);
288         }
289     }
290 }
291