SliceManager.java revision bf3eedcc6b340bc03c72cdb45305a8f93dd5b382
1/* 2 * Copyright (C) 2017 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.app.slice; 18 19import static android.content.pm.PackageManager.PERMISSION_DENIED; 20 21import android.annotation.NonNull; 22import android.annotation.Nullable; 23import android.annotation.SdkConstant; 24import android.annotation.SdkConstant.SdkConstantType; 25import android.annotation.SystemService; 26import android.content.ContentProviderClient; 27import android.content.ContentResolver; 28import android.content.Context; 29import android.content.Intent; 30import android.content.pm.PackageManager; 31import android.content.pm.PackageManager.PermissionResult; 32import android.content.pm.ResolveInfo; 33import android.net.Uri; 34import android.os.Binder; 35import android.os.Bundle; 36import android.os.Handler; 37import android.os.IBinder; 38import android.os.Process; 39import android.os.RemoteException; 40import android.os.ServiceManager; 41import android.os.ServiceManager.ServiceNotFoundException; 42import android.os.UserHandle; 43import android.util.Log; 44 45import com.android.internal.util.Preconditions; 46 47import java.util.ArrayList; 48import java.util.Arrays; 49import java.util.Collection; 50import java.util.Collections; 51import java.util.List; 52 53/** 54 * Class to handle interactions with {@link Slice}s. 55 * <p> 56 * The SliceManager manages permissions and pinned state for slices. 57 */ 58@SystemService(Context.SLICE_SERVICE) 59public class SliceManager { 60 61 private static final String TAG = "SliceManager"; 62 63 /** 64 * @hide 65 */ 66 public static final String ACTION_REQUEST_SLICE_PERMISSION = 67 "android.intent.action.REQUEST_SLICE_PERMISSION"; 68 69 /** 70 * Category used to resolve intents that can be rendered as slices. 71 * <p> 72 * This category should be included on intent filters on providers that extend 73 * {@link SliceProvider}. 74 * @see SliceProvider 75 * @see SliceProvider#onMapIntentToUri(Intent) 76 * @see #mapIntentToUri(Intent) 77 */ 78 @SdkConstant(SdkConstantType.INTENT_CATEGORY) 79 public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE"; 80 81 /** 82 * The meta-data key that allows an activity to easily be linked directly to a slice. 83 * <p> 84 * An activity can be statically linked to a slice uri by including a meta-data item 85 * for this key that contains a valid slice uri for the same application declaring 86 * the activity. 87 * 88 * <pre class="prettyprint"> 89 * {@literal 90 * <activity android:name="com.example.mypkg.MyActivity"> 91 * <meta-data android:name="android.metadata.SLICE_URI" 92 * android:value="content://com.example.mypkg/main_slice" /> 93 * </activity>} 94 * </pre> 95 * 96 * @see #mapIntentToUri(Intent) 97 * @see SliceProvider#onMapIntentToUri(Intent) 98 */ 99 public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI"; 100 101 private final ISliceManager mService; 102 private final Context mContext; 103 private final IBinder mToken = new Binder(); 104 105 /** 106 * @hide 107 */ 108 public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { 109 mContext = context; 110 mService = ISliceManager.Stub.asInterface( 111 ServiceManager.getServiceOrThrow(Context.SLICE_SERVICE)); 112 } 113 114 /** 115 * Ensures that a slice is in a pinned state. 116 * <p> 117 * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices 118 * they still care about after a reboot. 119 * <p> 120 * This may only be called by apps that are the default launcher for the device 121 * or the default voice interaction service. Otherwise will throw {@link SecurityException}. 122 * 123 * @param uri The uri of the slice being pinned. 124 * @param specs The list of supported {@link SliceSpec}s of the callback. 125 * @see SliceProvider#onSlicePinned(Uri) 126 * @see Intent#ACTION_ASSIST 127 * @see Intent#CATEGORY_HOME 128 */ 129 public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { 130 try { 131 mService.pinSlice(mContext.getPackageName(), uri, 132 specs.toArray(new SliceSpec[specs.size()]), mToken); 133 } catch (RemoteException e) { 134 throw e.rethrowFromSystemServer(); 135 } 136 } 137 138 /** 139 * Remove a pin for a slice. 140 * <p> 141 * If the slice has no other pins/callbacks then the slice will be unpinned. 142 * <p> 143 * This may only be called by apps that are the default launcher for the device 144 * or the default voice interaction service. Otherwise will throw {@link SecurityException}. 145 * 146 * @param uri The uri of the slice being unpinned. 147 * @see #pinSlice 148 * @see SliceProvider#onSliceUnpinned(Uri) 149 * @see Intent#ACTION_ASSIST 150 * @see Intent#CATEGORY_HOME 151 */ 152 public void unpinSlice(@NonNull Uri uri) { 153 try { 154 mService.unpinSlice(mContext.getPackageName(), uri, mToken); 155 } catch (RemoteException e) { 156 throw e.rethrowFromSystemServer(); 157 } 158 } 159 160 /** 161 * @hide 162 */ 163 public boolean hasSliceAccess() { 164 try { 165 return mService.hasSliceAccess(mContext.getPackageName()); 166 } catch (RemoteException e) { 167 throw e.rethrowFromSystemServer(); 168 } 169 } 170 171 /** 172 * Get the current set of specs for a pinned slice. 173 * <p> 174 * This is the set of specs supported for a specific pinned slice. It will take 175 * into account all clients and returns only specs supported by all. 176 * @see SliceSpec 177 */ 178 public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) { 179 try { 180 return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName())); 181 } catch (RemoteException e) { 182 throw e.rethrowFromSystemServer(); 183 } 184 } 185 186 /** 187 * Get the list of currently pinned slices for this app. 188 * @see SliceProvider#onSlicePinned 189 */ 190 public @NonNull List<Uri> getPinnedSlices() { 191 try { 192 return Arrays.asList(mService.getPinnedSlices(mContext.getPackageName())); 193 } catch (RemoteException e) { 194 throw e.rethrowFromSystemServer(); 195 } 196 } 197 198 /** 199 * Obtains a list of slices that are descendants of the specified Uri. 200 * <p> 201 * Not all slice providers will implement this functionality, in which case, 202 * an empty collection will be returned. 203 * 204 * @param uri The uri to look for descendants under. 205 * @return All slices within the space. 206 * @see SliceProvider#onGetSliceDescendants(Uri) 207 */ 208 public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) { 209 ContentResolver resolver = mContext.getContentResolver(); 210 try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { 211 Bundle extras = new Bundle(); 212 extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); 213 final Bundle res = provider.call(SliceProvider.METHOD_GET_DESCENDANTS, null, extras); 214 return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS); 215 } catch (RemoteException e) { 216 Log.e(TAG, "Unable to get slice descendants", e); 217 } 218 return Collections.emptyList(); 219 } 220 221 /** 222 * Turns a slice Uri into slice content. 223 * 224 * @param uri The URI to a slice provider 225 * @param supportedSpecs List of supported specs. 226 * @return The Slice provided by the app or null if none is given. 227 * @see Slice 228 */ 229 public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { 230 Preconditions.checkNotNull(uri, "uri"); 231 ContentResolver resolver = mContext.getContentResolver(); 232 try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { 233 if (provider == null) { 234 throw new IllegalArgumentException("Unknown URI " + uri); 235 } 236 Bundle extras = new Bundle(); 237 extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); 238 extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, 239 new ArrayList<>(supportedSpecs)); 240 final Bundle res = provider.call(SliceProvider.METHOD_SLICE, null, extras); 241 Bundle.setDefusable(res, true); 242 if (res == null) { 243 return null; 244 } 245 return res.getParcelable(SliceProvider.EXTRA_SLICE); 246 } catch (RemoteException e) { 247 // Arbitrary and not worth documenting, as Activity 248 // Manager will kill this process shortly anyway. 249 return null; 250 } 251 } 252 253 /** 254 * Turns a slice intent into a slice uri. Expects an explicit intent. 255 * <p> 256 * This goes through a several stage resolution process to determine if any slice 257 * can represent this intent. 258 * <ol> 259 * <li> If the intent contains data that {@link ContentResolver#getType} is 260 * {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li> 261 * <li>If the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then 262 * the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result 263 * will be returned.</li> 264 * <li>Lastly, if the intent explicitly points at an activity, and that activity has 265 * meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be 266 * returned.</li> 267 * <li>If no slice is found, then {@code null} is returned.</li> 268 * </ol> 269 * @param intent The intent associated with a slice. 270 * @return The Slice Uri provided by the app or null if none exists. 271 * @see Slice 272 * @see SliceProvider#onMapIntentToUri(Intent) 273 * @see Intent 274 */ 275 public @Nullable Uri mapIntentToUri(@NonNull Intent intent) { 276 Preconditions.checkNotNull(intent, "intent"); 277 Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null 278 || intent.getData() != null, 279 "Slice intent must be explicit %s", intent); 280 ContentResolver resolver = mContext.getContentResolver(); 281 282 // Check if the intent has data for the slice uri on it and use that 283 final Uri intentData = intent.getData(); 284 if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { 285 return intentData; 286 } 287 // Otherwise ask the app 288 Intent queryIntent = new Intent(intent); 289 if (!queryIntent.hasCategory(CATEGORY_SLICE)) { 290 queryIntent.addCategory(CATEGORY_SLICE); 291 } 292 List<ResolveInfo> providers = 293 mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0); 294 if (providers == null || providers.isEmpty()) { 295 // There are no providers, see if this activity has a direct link. 296 ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, 297 PackageManager.GET_META_DATA); 298 if (resolve != null && resolve.activityInfo != null 299 && resolve.activityInfo.metaData != null 300 && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { 301 return Uri.parse( 302 resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)); 303 } 304 return null; 305 } 306 String authority = providers.get(0).providerInfo.authority; 307 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 308 .authority(authority).build(); 309 try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { 310 if (provider == null) { 311 throw new IllegalArgumentException("Unknown URI " + uri); 312 } 313 Bundle extras = new Bundle(); 314 extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); 315 final Bundle res = provider.call(SliceProvider.METHOD_MAP_ONLY_INTENT, null, extras); 316 if (res == null) { 317 return null; 318 } 319 return res.getParcelable(SliceProvider.EXTRA_SLICE); 320 } catch (RemoteException e) { 321 // Arbitrary and not worth documenting, as Activity 322 // Manager will kill this process shortly anyway. 323 return null; 324 } 325 } 326 327 /** 328 * Turns a slice intent into slice content. Expects an explicit intent. If there is no 329 * {@link android.content.ContentProvider} associated with the given intent this will throw 330 * {@link IllegalArgumentException}. 331 * 332 * @param intent The intent associated with a slice. 333 * @param supportedSpecs List of supported specs. 334 * @return The Slice provided by the app or null if none is given. 335 * @see Slice 336 * @see SliceProvider#onMapIntentToUri(Intent) 337 * @see Intent 338 */ 339 public @Nullable Slice bindSlice(@NonNull Intent intent, 340 @NonNull List<SliceSpec> supportedSpecs) { 341 Preconditions.checkNotNull(intent, "intent"); 342 Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null 343 || intent.getData() != null, 344 "Slice intent must be explicit %s", intent); 345 ContentResolver resolver = mContext.getContentResolver(); 346 347 // Check if the intent has data for the slice uri on it and use that 348 final Uri intentData = intent.getData(); 349 if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { 350 return bindSlice(intentData, supportedSpecs); 351 } 352 // Otherwise ask the app 353 List<ResolveInfo> providers = 354 mContext.getPackageManager().queryIntentContentProviders(intent, 0); 355 if (providers == null || providers.isEmpty()) { 356 // There are no providers, see if this activity has a direct link. 357 ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent, 358 PackageManager.GET_META_DATA); 359 if (resolve != null && resolve.activityInfo != null 360 && resolve.activityInfo.metaData != null 361 && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { 362 return bindSlice(Uri.parse(resolve.activityInfo.metaData 363 .getString(SLICE_METADATA_KEY)), supportedSpecs); 364 } 365 return null; 366 } 367 String authority = providers.get(0).providerInfo.authority; 368 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 369 .authority(authority).build(); 370 try (ContentProviderClient provider = resolver.acquireContentProviderClient(uri)) { 371 if (provider == null) { 372 throw new IllegalArgumentException("Unknown URI " + uri); 373 } 374 Bundle extras = new Bundle(); 375 extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); 376 extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, 377 new ArrayList<>(supportedSpecs)); 378 final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras); 379 if (res == null) { 380 return null; 381 } 382 return res.getParcelable(SliceProvider.EXTRA_SLICE); 383 } catch (RemoteException e) { 384 // Arbitrary and not worth documenting, as Activity 385 // Manager will kill this process shortly anyway. 386 return null; 387 } 388 } 389 390 /** 391 * Determine whether a particular process and user ID has been granted 392 * permission to access a specific slice URI. 393 * 394 * @param uri The uri that is being checked. 395 * @param pid The process ID being checked against. Must be > 0. 396 * @param uid The user ID being checked against. A uid of 0 is the root 397 * user, which will pass every permission check. 398 * 399 * @return {@link PackageManager#PERMISSION_GRANTED} if the given 400 * pid/uid is allowed to access that uri, or 401 * {@link PackageManager#PERMISSION_DENIED} if it is not. 402 * 403 * @see #grantSlicePermission(String, Uri) 404 */ 405 public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) { 406 try { 407 return mService.checkSlicePermission(uri, null, pid, uid, null); 408 } catch (RemoteException e) { 409 throw e.rethrowFromSystemServer(); 410 } 411 } 412 413 /** 414 * Grant permission to access a specific slice Uri to another package. 415 * 416 * @param toPackage The package you would like to allow to access the Uri. 417 * @param uri The Uri you would like to grant access to. 418 * 419 * @see #revokeSlicePermission 420 */ 421 public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { 422 try { 423 mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri); 424 } catch (RemoteException e) { 425 throw e.rethrowFromSystemServer(); 426 } 427 } 428 429 /** 430 * Remove permissions to access a particular content provider Uri 431 * that were previously added with {@link #grantSlicePermission} for a specific target 432 * package. The given Uri will match all previously granted Uris that are the same or a 433 * sub-path of the given Uri. That is, revoking "content://foo/target" will 434 * revoke both "content://foo/target" and "content://foo/target/sub", but not 435 * "content://foo". It will not remove any prefix grants that exist at a 436 * higher level. 437 * 438 * @param toPackage The package you would like to allow to access the Uri. 439 * @param uri The Uri you would like to revoke access to. 440 * 441 * @see #grantSlicePermission 442 */ 443 public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) { 444 try { 445 mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri); 446 } catch (RemoteException e) { 447 throw e.rethrowFromSystemServer(); 448 } 449 } 450 451 /** 452 * Does the permission check to see if a caller has access to a specific slice. 453 * @hide 454 */ 455 public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid, 456 String[] autoGrantPermissions) { 457 try { 458 if (UserHandle.isSameApp(uid, Process.myUid())) { 459 return; 460 } 461 if (pkg == null) { 462 throw new SecurityException("No pkg specified"); 463 } 464 int result = mService.checkSlicePermission(uri, pkg, pid, uid, autoGrantPermissions); 465 if (result == PERMISSION_DENIED) { 466 throw new SecurityException("User " + uid + " does not have slice permission for " 467 + uri + "."); 468 } 469 } catch (RemoteException e) { 470 throw e.rethrowFromSystemServer(); 471 } 472 } 473 474 /** 475 * Called by SystemUI to grant a slice permission after a dialog is shown. 476 * @hide 477 */ 478 public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) { 479 try { 480 mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices); 481 } catch (RemoteException e) { 482 throw e.rethrowFromSystemServer(); 483 } 484 } 485} 486