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