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