blob: 02d0a6d8bd36081fa7ea32fc4b232df2b1400123 [file] [log] [blame]
Svet Ganovae0e03a2016-02-25 18:22:10 -08001/*
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
17package android.content.pm.permission;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
Svet Ganovae0e03a2016-02-25 18:22:10 -080025import android.os.Bundle;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.RemoteCallback;
30import android.os.RemoteException;
31import android.permissionpresenterservice.RuntimePermissionPresenterService;
32import android.util.Log;
jackqdyuleia90bfb52017-06-22 16:27:29 -070033
Svet Ganovae0e03a2016-02-25 18:22:10 -080034import com.android.internal.annotations.GuardedBy;
35import com.android.internal.os.SomeArgs;
36
37import java.util.ArrayList;
38import java.util.Collections;
39import 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 */
52public 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 */
71 public void onGetAppPermissions(@NonNull
72 List<RuntimePermissionPresentationInfo> permissions) {
73 /* do nothing - stub */
74 }
Svet Ganovae0e03a2016-02-25 18:22:10 -080075 }
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 */
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
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 */
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
jackqdyuleia90bfb52017-06-22 16:27:29 -0700121 /**
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 */
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
Svet Ganovae0e03a2016-02-25 18:22:10 -0800137 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;
jackqdyuleia90bfb52017-06-22 16:27:29 -0700144 public static final int MSG_REVOKE_APP_PERMISSIONS = 4;
Svet Ganovae0e03a2016-02-25 18:22:10 -0800145
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
159 public RemoteService(Context context) {
160 super(context.getMainLooper(), null, false);
161 mContext = context;
162 }
163
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
180 public void onServiceConnected(ComponentName name, IBinder service) {
181 synchronized (mLock) {
182 mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service);
183 scheduleNextMessageIfNeededLocked();
184 }
185 }
186
187 @Override
188 public void onServiceDisconnected(ComponentName name) {
189 synchronized (mLock) {
190 mRemoteInstance = null;
191 }
192 }
193
194 @Override
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
Svet Ganovae0e03a2016-02-25 18:22:10 -0800242 case MSG_UNBIND: {
243 synchronized (mLock) {
244 if (mBound) {
245 mContext.unbindService(this);
246 mBound = false;
247 }
248 mRemoteInstance = null;
249 }
250 } break;
jackqdyuleia90bfb52017-06-22 16:27:29 -0700251
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;
Svet Ganovae0e03a2016-02-25 18:22:10 -0800270 }
271
272 synchronized (mLock) {
273 scheduleNextMessageIfNeededLocked();
274 }
275 }
276
277 private void scheduleNextMessageIfNeededLocked() {
278 if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) {
279 Message nextMessage = mPendingWork.remove(0);
280 sendMessage(nextMessage);
281 }
282 }
283
284 private void scheduleUnbind() {
285 removeMessages(MSG_UNBIND);
286 sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS);
287 }
288 }
289}