SliceProvider.java revision 7f01f3b6198465c70e81c5edcc1be2fa8199efd8
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 */ 16package android.app.slice; 17 18import android.annotation.NonNull; 19import android.app.PendingIntent; 20import android.content.ComponentName; 21import android.content.ContentProvider; 22import android.content.ContentResolver; 23import android.content.ContentValues; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 27import android.content.pm.PackageManager; 28import android.content.pm.PackageManager.NameNotFoundException; 29import android.content.pm.ProviderInfo; 30import android.database.ContentObserver; 31import android.database.Cursor; 32import android.net.Uri; 33import android.os.Binder; 34import android.os.Bundle; 35import android.os.CancellationSignal; 36import android.os.Handler; 37import android.os.Process; 38import android.os.StrictMode; 39import android.os.StrictMode.ThreadPolicy; 40import android.util.ArraySet; 41import android.util.Log; 42 43import java.util.ArrayList; 44import java.util.Arrays; 45import java.util.Collection; 46import java.util.Collections; 47import java.util.List; 48import java.util.Set; 49 50/** 51 * A SliceProvider allows an app to provide content to be displayed in system spaces. This content 52 * is templated and can contain actions, and the behavior of how it is surfaced is specific to the 53 * system surface. 54 * <p> 55 * Slices are not currently live content. They are bound once and shown to the user. If the content 56 * changes due to a callback from user interaction, then 57 * {@link ContentResolver#notifyChange(Uri, ContentObserver)} should be used to notify the system. 58 * </p> 59 * <p> 60 * The provider needs to be declared in the manifest to provide the authority for the app. The 61 * authority for most slices is expected to match the package of the application. 62 * </p> 63 * 64 * <pre class="prettyprint"> 65 * {@literal 66 * <provider 67 * android:name="com.example.mypkg.MySliceProvider" 68 * android:authorities="com.example.mypkg" />} 69 * </pre> 70 * <p> 71 * Slices can be identified by a Uri or by an Intent. To link an Intent with a slice, the provider 72 * must have an {@link IntentFilter} matching the slice intent. When a slice is being requested via 73 * an intent, {@link #onMapIntentToUri(Intent)} can be called and is expected to return an 74 * appropriate Uri representing the slice. 75 * 76 * <pre class="prettyprint"> 77 * {@literal 78 * <provider 79 * android:name="com.example.mypkg.MySliceProvider" 80 * android:authorities="com.example.mypkg"> 81 * <intent-filter> 82 * <action android:name="com.example.mypkg.intent.action.MY_SLICE_INTENT" /> 83 * <category android:name="android.app.slice.category.SLICE" /> 84 * </intent-filter> 85 * </provider>} 86 * </pre> 87 * 88 * @see Slice 89 */ 90public abstract class SliceProvider extends ContentProvider { 91 /** 92 * This is the Android platform's MIME type for a URI 93 * containing a slice implemented through {@link SliceProvider}. 94 */ 95 public static final String SLICE_TYPE = "vnd.android.slice"; 96 97 private static final String TAG = "SliceProvider"; 98 /** 99 * @hide 100 */ 101 public static final String EXTRA_BIND_URI = "slice_uri"; 102 /** 103 * @hide 104 */ 105 public static final String EXTRA_SUPPORTED_SPECS = "supported_specs"; 106 /** 107 * @hide 108 */ 109 public static final String METHOD_SLICE = "bind_slice"; 110 /** 111 * @hide 112 */ 113 public static final String METHOD_MAP_INTENT = "map_slice"; 114 /** 115 * @hide 116 */ 117 public static final String METHOD_MAP_ONLY_INTENT = "map_only"; 118 /** 119 * @hide 120 */ 121 public static final String METHOD_PIN = "pin"; 122 /** 123 * @hide 124 */ 125 public static final String METHOD_UNPIN = "unpin"; 126 /** 127 * @hide 128 */ 129 public static final String METHOD_GET_DESCENDANTS = "get_descendants"; 130 /** 131 * @hide 132 */ 133 public static final String METHOD_GET_PERMISSIONS = "get_permissions"; 134 /** 135 * @hide 136 */ 137 public static final String EXTRA_INTENT = "slice_intent"; 138 /** 139 * @hide 140 */ 141 public static final String EXTRA_SLICE = "slice"; 142 /** 143 * @hide 144 */ 145 public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants"; 146 /** 147 * @hide 148 */ 149 public static final String EXTRA_PKG = "pkg"; 150 /** 151 * @hide 152 */ 153 public static final String EXTRA_PROVIDER_PKG = "provider_pkg"; 154 /** 155 * @hide 156 */ 157 public static final String EXTRA_RESULT = "result"; 158 159 private static final boolean DEBUG = false; 160 161 private static final long SLICE_BIND_ANR = 2000; 162 private final String[] mAutoGrantPermissions; 163 164 private String mCallback; 165 private SliceManager mSliceManager; 166 167 /** 168 * A version of constructing a SliceProvider that allows autogranting slice permissions 169 * to apps that hold specific platform permissions. 170 * <p> 171 * When an app tries to bind a slice from this provider that it does not have access to, 172 * This provider will check if the caller holds permissions to any of the autoGrantPermissions 173 * specified, if they do they will be granted persisted uri access to all slices of this 174 * provider. 175 * 176 * @param autoGrantPermissions List of permissions that holders are auto-granted access 177 * to slices. 178 */ 179 public SliceProvider(@NonNull String... autoGrantPermissions) { 180 mAutoGrantPermissions = autoGrantPermissions; 181 } 182 183 public SliceProvider() { 184 mAutoGrantPermissions = new String[0]; 185 } 186 187 @Override 188 public void attachInfo(Context context, ProviderInfo info) { 189 super.attachInfo(context, info); 190 mSliceManager = context.getSystemService(SliceManager.class); 191 } 192 193 /** 194 * Implemented to create a slice. 195 * <p> 196 * onBindSlice should return as quickly as possible so that the UI tied 197 * to this slice can be responsive. No network or other IO will be allowed 198 * during onBindSlice. Any loading that needs to be done should happen 199 * in the background with a call to {@link ContentResolver#notifyChange(Uri, ContentObserver)} 200 * when the app is ready to provide the complete data in onBindSlice. 201 * <p> 202 * The slice returned should have a spec that is compatible with one of 203 * the supported specs. 204 * 205 * @param sliceUri Uri to bind. 206 * @param supportedSpecs List of supported specs. 207 * @see {@link Slice}. 208 * @see {@link Slice#HINT_PARTIAL} 209 */ 210 public Slice onBindSlice(Uri sliceUri, Set<SliceSpec> supportedSpecs) { 211 return onBindSlice(sliceUri, new ArrayList<>(supportedSpecs)); 212 } 213 214 /** 215 * @deprecated TO BE REMOVED 216 * @removed 217 */ 218 @Deprecated 219 public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) { 220 return null; 221 } 222 223 /** 224 * Called to inform an app that a slice has been pinned. 225 * <p> 226 * Pinning is a way that slice hosts use to notify apps of which slices 227 * they care about updates for. When a slice is pinned the content is 228 * expected to be relatively fresh and kept up to date. 229 * <p> 230 * Being pinned does not provide any escalated privileges for the slice 231 * provider. So apps should do things such as turn on syncing or schedule 232 * a job in response to a onSlicePinned. 233 * <p> 234 * Pinned state is not persisted through a reboot, and apps can expect a 235 * new call to onSlicePinned for any slices that should remain pinned 236 * after a reboot occurs. 237 * 238 * @param sliceUri The uri of the slice being unpinned. 239 * @see #onSliceUnpinned(Uri) 240 */ 241 public void onSlicePinned(Uri sliceUri) { 242 } 243 244 /** 245 * Called to inform an app that a slices is no longer pinned. 246 * <p> 247 * This means that no other apps on the device care about updates to this 248 * slice anymore and therefore it is not important to be updated. Any syncs 249 * or jobs related to this slice should be cancelled. 250 * @see #onSlicePinned(Uri) 251 */ 252 public void onSliceUnpinned(Uri sliceUri) { 253 } 254 255 /** 256 * Obtains a list of slices that are descendants of the specified Uri. 257 * <p> 258 * Implementing this is optional for a SliceProvider, but does provide a good 259 * discovery mechanism for finding slice Uris. 260 * 261 * @param uri The uri to look for descendants under. 262 * @return All slices within the space. 263 * @see SliceManager#getSliceDescendants(Uri) 264 */ 265 public @NonNull Collection<Uri> onGetSliceDescendants(@NonNull Uri uri) { 266 return Collections.emptyList(); 267 } 268 269 /** 270 * This method must be overridden if an {@link IntentFilter} is specified on the SliceProvider. 271 * In that case, this method can be called and is expected to return a non-null Uri representing 272 * a slice. Otherwise this will throw {@link UnsupportedOperationException}. 273 * 274 * Any intent filter added to a slice provider should also contain 275 * {@link SliceManager#CATEGORY_SLICE}, because otherwise it will not be detected by 276 * {@link SliceManager#mapIntentToUri(Intent)}. 277 * 278 * @return Uri representing the slice associated with the provided intent. 279 * @see Slice 280 * @see SliceManager#mapIntentToUri(Intent) 281 */ 282 public @NonNull Uri onMapIntentToUri(Intent intent) { 283 throw new UnsupportedOperationException( 284 "This provider has not implemented intent to uri mapping"); 285 } 286 287 /** 288 * Called when an app requests a slice it does not have write permission 289 * to the uri for. 290 * <p> 291 * The return value will be the action on a slice that prompts the user that 292 * the calling app wants to show slices from this app. The default implementation 293 * launches a dialog that allows the user to grant access to this slice. Apps 294 * that do not want to allow this user grant, can override this and instead 295 * launch their own dialog with different behavior. 296 * 297 * @param sliceUri the Uri of the slice attempting to be bound. 298 * @see #getCallingPackage() 299 */ 300 public @NonNull PendingIntent onCreatePermissionRequest(Uri sliceUri) { 301 return createPermissionIntent(getContext(), sliceUri, getCallingPackage()); 302 } 303 304 @Override 305 public final int update(Uri uri, ContentValues values, String selection, 306 String[] selectionArgs) { 307 if (DEBUG) Log.d(TAG, "update " + uri); 308 return 0; 309 } 310 311 @Override 312 public final int delete(Uri uri, String selection, String[] selectionArgs) { 313 if (DEBUG) Log.d(TAG, "delete " + uri); 314 return 0; 315 } 316 317 @Override 318 public final Cursor query(Uri uri, String[] projection, String selection, 319 String[] selectionArgs, String sortOrder) { 320 if (DEBUG) Log.d(TAG, "query " + uri); 321 return null; 322 } 323 324 @Override 325 public final Cursor query(Uri uri, String[] projection, String selection, String[] 326 selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { 327 if (DEBUG) Log.d(TAG, "query " + uri); 328 return null; 329 } 330 331 @Override 332 public final Cursor query(Uri uri, String[] projection, Bundle queryArgs, 333 CancellationSignal cancellationSignal) { 334 if (DEBUG) Log.d(TAG, "query " + uri); 335 return null; 336 } 337 338 @Override 339 public final Uri insert(Uri uri, ContentValues values) { 340 if (DEBUG) Log.d(TAG, "insert " + uri); 341 return null; 342 } 343 344 @Override 345 public final String getType(Uri uri) { 346 if (DEBUG) Log.d(TAG, "getType " + uri); 347 return SLICE_TYPE; 348 } 349 350 @Override 351 public Bundle call(String method, String arg, Bundle extras) { 352 if (method.equals(METHOD_SLICE)) { 353 Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); 354 List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); 355 356 String callingPackage = getCallingPackage(); 357 int callingUid = Binder.getCallingUid(); 358 int callingPid = Binder.getCallingPid(); 359 360 Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid); 361 Bundle b = new Bundle(); 362 b.putParcelable(EXTRA_SLICE, s); 363 return b; 364 } else if (method.equals(METHOD_MAP_INTENT)) { 365 Intent intent = extras.getParcelable(EXTRA_INTENT); 366 if (intent == null) return null; 367 Uri uri = onMapIntentToUri(intent); 368 List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS); 369 Bundle b = new Bundle(); 370 if (uri != null) { 371 Slice s = handleBindSlice(uri, supportedSpecs, getCallingPackage(), 372 Binder.getCallingUid(), Binder.getCallingPid()); 373 b.putParcelable(EXTRA_SLICE, s); 374 } else { 375 b.putParcelable(EXTRA_SLICE, null); 376 } 377 return b; 378 } else if (method.equals(METHOD_MAP_ONLY_INTENT)) { 379 Intent intent = extras.getParcelable(EXTRA_INTENT); 380 if (intent == null) return null; 381 Uri uri = onMapIntentToUri(intent); 382 Bundle b = new Bundle(); 383 b.putParcelable(EXTRA_SLICE, uri); 384 return b; 385 } else if (method.equals(METHOD_PIN)) { 386 Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); 387 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 388 throw new SecurityException("Only the system can pin/unpin slices"); 389 } 390 handlePinSlice(uri); 391 } else if (method.equals(METHOD_UNPIN)) { 392 Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); 393 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 394 throw new SecurityException("Only the system can pin/unpin slices"); 395 } 396 handleUnpinSlice(uri); 397 } else if (method.equals(METHOD_GET_DESCENDANTS)) { 398 Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI)); 399 Bundle b = new Bundle(); 400 b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, 401 new ArrayList<>(handleGetDescendants(uri))); 402 return b; 403 } else if (method.equals(METHOD_GET_PERMISSIONS)) { 404 if (Binder.getCallingUid() != Process.SYSTEM_UID) { 405 throw new SecurityException("Only the system can get permissions"); 406 } 407 Bundle b = new Bundle(); 408 b.putStringArray(EXTRA_RESULT, mAutoGrantPermissions); 409 return b; 410 } 411 return super.call(method, arg, extras); 412 } 413 414 private Collection<Uri> handleGetDescendants(Uri uri) { 415 mCallback = "onGetSliceDescendants"; 416 return onGetSliceDescendants(uri); 417 } 418 419 private void handlePinSlice(Uri sliceUri) { 420 mCallback = "onSlicePinned"; 421 Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); 422 try { 423 onSlicePinned(sliceUri); 424 } finally { 425 Handler.getMain().removeCallbacks(mAnr); 426 } 427 } 428 429 private void handleUnpinSlice(Uri sliceUri) { 430 mCallback = "onSliceUnpinned"; 431 Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); 432 try { 433 onSliceUnpinned(sliceUri); 434 } finally { 435 Handler.getMain().removeCallbacks(mAnr); 436 } 437 } 438 439 private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs, 440 String callingPkg, int callingUid, int callingPid) { 441 // This can be removed once Slice#bindSlice is removed and everyone is using 442 // SliceManager#bindSlice. 443 String pkg = callingPkg != null ? callingPkg 444 : getContext().getPackageManager().getNameForUid(callingUid); 445 try { 446 mSliceManager.enforceSlicePermission(sliceUri, pkg, 447 callingPid, callingUid, mAutoGrantPermissions); 448 } catch (SecurityException e) { 449 return createPermissionSlice(getContext(), sliceUri, pkg); 450 } 451 mCallback = "onBindSlice"; 452 Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); 453 try { 454 return onBindSliceStrict(sliceUri, supportedSpecs); 455 } finally { 456 Handler.getMain().removeCallbacks(mAnr); 457 } 458 } 459 460 /** 461 * @hide 462 */ 463 public Slice createPermissionSlice(Context context, Uri sliceUri, 464 String callingPackage) { 465 PendingIntent action; 466 mCallback = "onCreatePermissionRequest"; 467 Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR); 468 try { 469 action = onCreatePermissionRequest(sliceUri); 470 } finally { 471 Handler.getMain().removeCallbacks(mAnr); 472 } 473 Slice.Builder parent = new Slice.Builder(sliceUri); 474 Slice.Builder childAction = new Slice.Builder(parent) 475 .addHints(Arrays.asList(Slice.HINT_TITLE, Slice.HINT_SHORTCUT)) 476 .addAction(action, new Slice.Builder(parent).build(), null); 477 478 parent.addSubSlice(new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) 479 .addText(getPermissionString(context, callingPackage), null, 480 Collections.emptyList()) 481 .addSubSlice(childAction.build(), null) 482 .build(), null); 483 return parent.addHints(Arrays.asList(Slice.HINT_PERMISSION_REQUEST)).build(); 484 } 485 486 /** 487 * @hide 488 */ 489 public static PendingIntent createPermissionIntent(Context context, Uri sliceUri, 490 String callingPackage) { 491 Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION); 492 intent.setComponent(new ComponentName("com.android.systemui", 493 "com.android.systemui.SlicePermissionActivity")); 494 intent.putExtra(EXTRA_BIND_URI, sliceUri); 495 intent.putExtra(EXTRA_PKG, callingPackage); 496 intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName()); 497 // Unique pending intent. 498 intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage) 499 .build()); 500 501 return PendingIntent.getActivity(context, 0, intent, 0); 502 } 503 504 /** 505 * @hide 506 */ 507 public static CharSequence getPermissionString(Context context, String callingPackage) { 508 PackageManager pm = context.getPackageManager(); 509 try { 510 return context.getString( 511 com.android.internal.R.string.slices_permission_request, 512 pm.getApplicationInfo(callingPackage, 0).loadLabel(pm), 513 context.getApplicationInfo().loadLabel(pm)); 514 } catch (NameNotFoundException e) { 515 // This shouldn't be possible since the caller is verified. 516 throw new RuntimeException("Unknown calling app", e); 517 } 518 } 519 520 private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) { 521 ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 522 try { 523 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 524 .detectAll() 525 .penaltyDeath() 526 .build()); 527 return onBindSlice(sliceUri, new ArraySet<>(supportedSpecs)); 528 } finally { 529 StrictMode.setThreadPolicy(oldPolicy); 530 } 531 } 532 533 private final Runnable mAnr = () -> { 534 Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); 535 Log.wtf(TAG, "Timed out while handling slice callback " + mCallback); 536 }; 537} 538