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