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.content.pm.ApplicationInfo;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.IBinder;
29 import android.os.Message;
30 import android.os.RemoteCallback;
31 import android.os.RemoteException;
32 import android.permissionpresenterservice.RuntimePermissionPresenterService;
33 import android.util.Log;
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     private static final class RemoteService
122             extends Handler implements ServiceConnection {
123         private static final long UNBIND_TIMEOUT_MILLIS = 10000;
124 
125         public static final int MSG_GET_APP_PERMISSIONS = 1;
126         public static final int MSG_GET_APPS_USING_PERMISSIONS = 2;
127         public static final int MSG_UNBIND = 3;
128 
129         private final Object mLock = new Object();
130 
131         private final Context mContext;
132 
133         @GuardedBy("mLock")
134         private final List<Message> mPendingWork = new ArrayList<>();
135 
136         @GuardedBy("mLock")
137         private IRuntimePermissionPresenter mRemoteInstance;
138 
139         @GuardedBy("mLock")
140         private boolean mBound;
141 
RemoteService(Context context)142         public RemoteService(Context context) {
143             super(context.getMainLooper(), null, false);
144             mContext = context;
145         }
146 
processMessage(Message message)147         public void processMessage(Message message) {
148             synchronized (mLock) {
149                 if (!mBound) {
150                     Intent intent = new Intent(
151                             RuntimePermissionPresenterService.SERVICE_INTERFACE);
152                     intent.setPackage(mContext.getPackageManager()
153                             .getPermissionControllerPackageName());
154                     mBound = mContext.bindService(intent, this,
155                             Context.BIND_AUTO_CREATE);
156                 }
157                 mPendingWork.add(message);
158                 scheduleNextMessageIfNeededLocked();
159             }
160         }
161 
162         @Override
onServiceConnected(ComponentName name, IBinder service)163         public void onServiceConnected(ComponentName name, IBinder service) {
164             synchronized (mLock) {
165                 mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service);
166                 scheduleNextMessageIfNeededLocked();
167             }
168         }
169 
170         @Override
onServiceDisconnected(ComponentName name)171         public void onServiceDisconnected(ComponentName name) {
172             synchronized (mLock) {
173                 mRemoteInstance = null;
174             }
175         }
176 
177         @Override
handleMessage(Message msg)178         public void handleMessage(Message msg) {
179             switch (msg.what) {
180                 case MSG_GET_APP_PERMISSIONS: {
181                     SomeArgs args = (SomeArgs) msg.obj;
182                     final String packageName = (String) args.arg1;
183                     final OnResultCallback callback = (OnResultCallback) args.arg2;
184                     final Handler handler = (Handler) args.arg3;
185                     args.recycle();
186                     final IRuntimePermissionPresenter remoteInstance;
187                     synchronized (mLock) {
188                         remoteInstance = mRemoteInstance;
189                     }
190                     if (remoteInstance == null) {
191                         return;
192                     }
193                     try {
194                         remoteInstance.getAppPermissions(packageName,
195                                 new RemoteCallback(new RemoteCallback.OnResultListener() {
196                             @Override
197                             public void onResult(Bundle result) {
198                                 final List<RuntimePermissionPresentationInfo> reportedPermissions;
199                                 List<RuntimePermissionPresentationInfo> permissions = null;
200                                 if (result != null) {
201                                     permissions = result.getParcelableArrayList(KEY_RESULT);
202                                 }
203                                 if (permissions == null) {
204                                     permissions = Collections.emptyList();
205                                 }
206                                 reportedPermissions = permissions;
207                                 if (handler != null) {
208                                     handler.post(new Runnable() {
209                                         @Override
210                                         public void run() {
211                                             callback.onGetAppPermissions(reportedPermissions);
212                                         }
213                                     });
214                                 } else {
215                                     callback.onGetAppPermissions(reportedPermissions);
216                                 }
217                             }
218                         }, this));
219                     } catch (RemoteException re) {
220                         Log.e(TAG, "Error getting app permissions", re);
221                     }
222                     scheduleUnbind();
223                 } break;
224 
225                 case MSG_UNBIND: {
226                     synchronized (mLock) {
227                         if (mBound) {
228                             mContext.unbindService(this);
229                             mBound = false;
230                         }
231                         mRemoteInstance = null;
232                     }
233                 } break;
234             }
235 
236             synchronized (mLock) {
237                 scheduleNextMessageIfNeededLocked();
238             }
239         }
240 
scheduleNextMessageIfNeededLocked()241         private void scheduleNextMessageIfNeededLocked() {
242             if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
243                 Message nextMessage = mPendingWork.remove(0);
244                 sendMessage(nextMessage);
245             }
246         }
247 
scheduleUnbind()248         private void scheduleUnbind() {
249             removeMessages(MSG_UNBIND);
250             sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS);
251         }
252     }
253 }
254