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 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; 25import android.content.pm.ApplicationInfo; 26import android.os.Bundle; 27import android.os.Handler; 28import android.os.IBinder; 29import android.os.Message; 30import android.os.RemoteCallback; 31import android.os.RemoteException; 32import android.permissionpresenterservice.RuntimePermissionPresenterService; 33import android.util.Log; 34import 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 } 75 76 /** 77 * The result for {@link #getAppsUsingPermissions(boolean, List)}. 78 * @param system Whether to return only the system apps or only the non-system ones. 79 * @param apps The apps using runtime permissions. 80 */ 81 public void getAppsUsingPermissions(boolean system, @NonNull List<ApplicationInfo> apps) { 82 /* do nothing - stub */ 83 } 84 } 85 86 private static final Object sLock = new Object(); 87 88 @GuardedBy("sLock") 89 private static RuntimePermissionPresenter sInstance; 90 91 private final RemoteService mRemoteService; 92 93 /** 94 * Gets the singleton runtime permission presenter. 95 * 96 * @param context Context for accessing resources. 97 * @return The singleton instance. 98 */ 99 public static RuntimePermissionPresenter getInstance(@NonNull Context context) { 100 synchronized (sLock) { 101 if (sInstance == null) { 102 sInstance = new RuntimePermissionPresenter(context.getApplicationContext()); 103 } 104 return sInstance; 105 } 106 } 107 108 private RuntimePermissionPresenter(Context context) { 109 mRemoteService = new RemoteService(context); 110 } 111 112 /** 113 * Gets the runtime permissions for an app. 114 * 115 * @param packageName The package for which to query. 116 * @param callback Callback to receive the result. 117 * @param handler Handler on which to invoke the callback. 118 */ 119 public void getAppPermissions(@NonNull String packageName, 120 @NonNull OnResultCallback callback, @Nullable Handler handler) { 121 SomeArgs args = SomeArgs.obtain(); 122 args.arg1 = packageName; 123 args.arg2 = callback; 124 args.arg3 = handler; 125 Message message = mRemoteService.obtainMessage( 126 RemoteService.MSG_GET_APP_PERMISSIONS, args); 127 mRemoteService.processMessage(message); 128 } 129 130 /** 131 * Gets the system apps that use runtime permissions. System apps are ones 132 * that are considered system for presentation purposes instead of ones 133 * that are preinstalled on the system image. System apps are ones that 134 * are on the system image, haven't been updated (a.k.a factory apps) 135 * that do not have a launcher icon. 136 * 137 * @param system If true only system apps are returned otherwise only 138 * non-system ones are returned. 139 * @param callback Callback to receive the result. 140 * @param handler Handler on which to invoke the callback. 141 */ 142 public void getAppsUsingPermissions(boolean system, @NonNull OnResultCallback callback, 143 @Nullable Handler handler) { 144 SomeArgs args = SomeArgs.obtain(); 145 args.arg1 = callback; 146 args.arg2 = handler; 147 args.argi1 = system ? 1 : 0; 148 Message message = mRemoteService.obtainMessage( 149 RemoteService.MSG_GET_APPS_USING_PERMISSIONS, args); 150 mRemoteService.processMessage(message); 151 } 152 153 private static final class RemoteService 154 extends Handler implements ServiceConnection { 155 private static final long UNBIND_TIMEOUT_MILLIS = 10000; 156 157 public static final int MSG_GET_APP_PERMISSIONS = 1; 158 public static final int MSG_GET_APPS_USING_PERMISSIONS = 2; 159 public static final int MSG_UNBIND = 3; 160 161 private final Object mLock = new Object(); 162 163 private final Context mContext; 164 165 @GuardedBy("mLock") 166 private final List<Message> mPendingWork = new ArrayList<>(); 167 168 @GuardedBy("mLock") 169 private IRuntimePermissionPresenter mRemoteInstance; 170 171 @GuardedBy("mLock") 172 private boolean mBound; 173 174 public RemoteService(Context context) { 175 super(context.getMainLooper(), null, false); 176 mContext = context; 177 } 178 179 public void processMessage(Message message) { 180 synchronized (mLock) { 181 if (!mBound) { 182 Intent intent = new Intent( 183 RuntimePermissionPresenterService.SERVICE_INTERFACE); 184 intent.setPackage(mContext.getPackageManager() 185 .getPermissionControllerPackageName()); 186 mBound = mContext.bindService(intent, this, 187 Context.BIND_AUTO_CREATE); 188 } 189 mPendingWork.add(message); 190 scheduleNextMessageIfNeededLocked(); 191 } 192 } 193 194 @Override 195 public void onServiceConnected(ComponentName name, IBinder service) { 196 synchronized (mLock) { 197 mRemoteInstance = IRuntimePermissionPresenter.Stub.asInterface(service); 198 scheduleNextMessageIfNeededLocked(); 199 } 200 } 201 202 @Override 203 public void onServiceDisconnected(ComponentName name) { 204 synchronized (mLock) { 205 mRemoteInstance = null; 206 } 207 } 208 209 @Override 210 public void handleMessage(Message msg) { 211 switch (msg.what) { 212 case MSG_GET_APP_PERMISSIONS: { 213 SomeArgs args = (SomeArgs) msg.obj; 214 final String packageName = (String) args.arg1; 215 final OnResultCallback callback = (OnResultCallback) args.arg2; 216 final Handler handler = (Handler) args.arg3; 217 args.recycle(); 218 final IRuntimePermissionPresenter remoteInstance; 219 synchronized (mLock) { 220 remoteInstance = mRemoteInstance; 221 } 222 if (remoteInstance == null) { 223 return; 224 } 225 try { 226 remoteInstance.getAppPermissions(packageName, 227 new RemoteCallback(new RemoteCallback.OnResultListener() { 228 @Override 229 public void onResult(Bundle result) { 230 final List<RuntimePermissionPresentationInfo> reportedPermissions; 231 List<RuntimePermissionPresentationInfo> permissions = null; 232 if (result != null) { 233 permissions = result.getParcelableArrayList(KEY_RESULT); 234 } 235 if (permissions == null) { 236 permissions = Collections.emptyList(); 237 } 238 reportedPermissions = permissions; 239 if (handler != null) { 240 handler.post(new Runnable() { 241 @Override 242 public void run() { 243 callback.onGetAppPermissions(reportedPermissions); 244 } 245 }); 246 } else { 247 callback.onGetAppPermissions(reportedPermissions); 248 } 249 } 250 }, this)); 251 } catch (RemoteException re) { 252 Log.e(TAG, "Error getting app permissions", re); 253 } 254 scheduleUnbind(); 255 } break; 256 257 case MSG_GET_APPS_USING_PERMISSIONS: { 258 SomeArgs args = (SomeArgs) msg.obj; 259 final OnResultCallback callback = (OnResultCallback) args.arg1; 260 final Handler handler = (Handler) args.arg2; 261 final boolean system = args.argi1 == 1; 262 args.recycle(); 263 final IRuntimePermissionPresenter remoteInstance; 264 synchronized (mLock) { 265 remoteInstance = mRemoteInstance; 266 } 267 if (remoteInstance == null) { 268 return; 269 } 270 try { 271 remoteInstance.getAppsUsingPermissions(system, new RemoteCallback( 272 new RemoteCallback.OnResultListener() { 273 @Override 274 public void onResult(Bundle result) { 275 final List<ApplicationInfo> reportedApps; 276 List<ApplicationInfo> apps = null; 277 if (result != null) { 278 apps = result.getParcelableArrayList(KEY_RESULT); 279 } 280 if (apps == null) { 281 apps = Collections.emptyList(); 282 } 283 reportedApps = apps; 284 if (handler != null) { 285 handler.post(new Runnable() { 286 @Override 287 public void run() { 288 callback.getAppsUsingPermissions(system, reportedApps); 289 } 290 }); 291 } else { 292 callback.getAppsUsingPermissions(system, reportedApps); 293 } 294 } 295 }, this)); 296 } catch (RemoteException re) { 297 Log.e(TAG, "Error getting apps using permissions", re); 298 } 299 scheduleUnbind(); 300 } break; 301 302 case MSG_UNBIND: { 303 synchronized (mLock) { 304 if (mBound) { 305 mContext.unbindService(this); 306 mBound = false; 307 } 308 mRemoteInstance = null; 309 } 310 } break; 311 } 312 313 synchronized (mLock) { 314 scheduleNextMessageIfNeededLocked(); 315 } 316 } 317 318 private void scheduleNextMessageIfNeededLocked() { 319 if (mBound && mRemoteInstance != null && !mPendingWork.isEmpty()) { 320 Message nextMessage = mPendingWork.remove(0); 321 sendMessage(nextMessage); 322 } 323 } 324 325 private void scheduleUnbind() { 326 removeMessages(MSG_UNBIND); 327 sendEmptyMessageDelayed(MSG_UNBIND, UNBIND_TIMEOUT_MILLIS); 328 } 329 } 330} 331