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