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