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