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