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 */ 16 package androidx.slice.compat; 17 18 import static android.app.slice.SliceManager.CATEGORY_SLICE; 19 import static android.app.slice.SliceManager.SLICE_METADATA_KEY; 20 import static android.app.slice.SliceProvider.SLICE_TYPE; 21 22 import static androidx.core.content.PermissionChecker.PERMISSION_DENIED; 23 import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED; 24 25 import android.content.ContentProviderClient; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.SharedPreferences; 30 import android.content.pm.PackageManager; 31 import android.content.pm.ResolveInfo; 32 import android.net.Uri; 33 import android.os.Binder; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.Looper; 38 import android.os.Parcelable; 39 import android.os.Process; 40 import android.os.RemoteException; 41 import android.os.StrictMode; 42 import android.util.Log; 43 44 import androidx.annotation.NonNull; 45 import androidx.annotation.RestrictTo; 46 import androidx.annotation.RestrictTo.Scope; 47 import androidx.collection.ArraySet; 48 import androidx.core.util.Preconditions; 49 import androidx.slice.Slice; 50 import androidx.slice.SliceProvider; 51 import androidx.slice.SliceSpec; 52 53 import java.util.ArrayList; 54 import java.util.Collection; 55 import java.util.Collections; 56 import java.util.List; 57 import java.util.Set; 58 59 /** 60 * @hide 61 */ 62 @RestrictTo(Scope.LIBRARY) 63 public class SliceProviderCompat { 64 public static final String PERMS_PREFIX = "slice_perms_"; 65 private static final String TAG = "SliceProviderCompat"; 66 private static final String DATA_PREFIX = "slice_data_"; 67 private static final String ALL_FILES = DATA_PREFIX + "all_slice_files"; 68 69 private static final long SLICE_BIND_ANR = 2000; 70 71 public static final String METHOD_SLICE = "bind_slice"; 72 public static final String METHOD_MAP_INTENT = "map_slice"; 73 public static final String METHOD_PIN = "pin_slice"; 74 public static final String METHOD_UNPIN = "unpin_slice"; 75 public static final String METHOD_GET_PINNED_SPECS = "get_specs"; 76 public static final String METHOD_MAP_ONLY_INTENT = "map_only"; 77 public static final String METHOD_GET_DESCENDANTS = "get_descendants"; 78 public static final String METHOD_CHECK_PERMISSION = "check_perms"; 79 public static final String METHOD_GRANT_PERMISSION = "grant_perms"; 80 public static final String METHOD_REVOKE_PERMISSION = "revoke_perms"; 81 82 public static final String EXTRA_BIND_URI = "slice_uri"; 83 public static final String EXTRA_INTENT = "slice_intent"; 84 public static final String EXTRA_SLICE = "slice"; 85 public static final String EXTRA_SUPPORTED_SPECS = "specs"; 86 public static final String EXTRA_SUPPORTED_SPECS_REVS = "revs"; 87 public static final String EXTRA_PKG = "pkg"; 88 public static final String EXTRA_PROVIDER_PKG = "provider_pkg"; 89 public static final String EXTRA_SLICE_DESCENDANTS = "slice_descendants"; 90 public static final String EXTRA_UID = "uid"; 91 public static final String EXTRA_PID = "pid"; 92 public static final String EXTRA_RESULT = "result"; 93 94 private final Handler mHandler = new Handler(Looper.getMainLooper()); 95 private final Context mContext; 96 97 private String mCallback; 98 private final SliceProvider mProvider; 99 private CompatPinnedList mPinnedList; 100 private CompatPermissionManager mPermissionManager; 101 SliceProviderCompat(SliceProvider provider, CompatPermissionManager permissionManager, Context context)102 public SliceProviderCompat(SliceProvider provider, CompatPermissionManager permissionManager, 103 Context context) { 104 mProvider = provider; 105 mContext = context; 106 String prefsFile = DATA_PREFIX + getClass().getName(); 107 SharedPreferences allFiles = mContext.getSharedPreferences(ALL_FILES, 0); 108 Set<String> files = allFiles.getStringSet(ALL_FILES, Collections.<String>emptySet()); 109 if (!files.contains(prefsFile)) { 110 // Make sure this is editable. 111 files = new ArraySet<>(files); 112 files.add(prefsFile); 113 allFiles.edit() 114 .putStringSet(ALL_FILES, files) 115 .commit(); 116 } 117 mPinnedList = new CompatPinnedList(mContext, prefsFile); 118 mPermissionManager = permissionManager; 119 } 120 getContext()121 private Context getContext() { 122 return mContext; 123 } 124 getCallingPackage()125 public String getCallingPackage() { 126 return mProvider.getCallingPackage(); 127 } 128 129 /** 130 * Called by SliceProvider when compat is needed. 131 */ call(String method, String arg, Bundle extras)132 public Bundle call(String method, String arg, Bundle extras) { 133 if (method.equals(METHOD_SLICE)) { 134 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 135 Set<SliceSpec> specs = getSpecs(extras); 136 137 Slice s = handleBindSlice(uri, specs, getCallingPackage()); 138 Bundle b = new Bundle(); 139 b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null); 140 return b; 141 } else if (method.equals(METHOD_MAP_INTENT)) { 142 Intent intent = extras.getParcelable(EXTRA_INTENT); 143 Uri uri = mProvider.onMapIntentToUri(intent); 144 Bundle b = new Bundle(); 145 if (uri != null) { 146 Set<SliceSpec> specs = getSpecs(extras); 147 Slice s = handleBindSlice(uri, specs, getCallingPackage()); 148 b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null); 149 } else { 150 b.putParcelable(EXTRA_SLICE, null); 151 } 152 return b; 153 } else if (method.equals(METHOD_MAP_ONLY_INTENT)) { 154 Intent intent = extras.getParcelable(EXTRA_INTENT); 155 Uri uri = mProvider.onMapIntentToUri(intent); 156 Bundle b = new Bundle(); 157 b.putParcelable(EXTRA_SLICE, uri); 158 return b; 159 } else if (method.equals(METHOD_PIN)) { 160 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 161 Set<SliceSpec> specs = getSpecs(extras); 162 String pkg = extras.getString(EXTRA_PKG); 163 if (mPinnedList.addPin(uri, pkg, specs)) { 164 handleSlicePinned(uri); 165 } 166 return null; 167 } else if (method.equals(METHOD_UNPIN)) { 168 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 169 String pkg = extras.getString(EXTRA_PKG); 170 if (mPinnedList.removePin(uri, pkg)) { 171 handleSliceUnpinned(uri); 172 } 173 return null; 174 } else if (method.equals(METHOD_GET_PINNED_SPECS)) { 175 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 176 Bundle b = new Bundle(); 177 addSpecs(b, mPinnedList.getSpecs(uri)); 178 return b; 179 } else if (method.equals(METHOD_GET_DESCENDANTS)) { 180 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 181 Bundle b = new Bundle(); 182 b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, 183 new ArrayList<>(handleGetDescendants(uri))); 184 return b; 185 } else if (method.equals(METHOD_CHECK_PERMISSION)) { 186 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 187 String pkg = extras.getString(EXTRA_PKG); 188 int pid = extras.getInt(EXTRA_PID); 189 int uid = extras.getInt(EXTRA_UID); 190 Bundle b = new Bundle(); 191 b.putInt(EXTRA_RESULT, mPermissionManager.checkSlicePermission(uri, pid, uid)); 192 return b; 193 } else if (method.equals(METHOD_GRANT_PERMISSION)) { 194 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 195 String toPkg = extras.getString(EXTRA_PKG); 196 if (Binder.getCallingUid() != Process.myUid()) { 197 throw new SecurityException("Only the owning process can manage slice permissions"); 198 } 199 mPermissionManager.grantSlicePermission(uri, toPkg); 200 } else if (method.equals(METHOD_REVOKE_PERMISSION)) { 201 Uri uri = extras.getParcelable(EXTRA_BIND_URI); 202 String toPkg = extras.getString(EXTRA_PKG); 203 if (Binder.getCallingUid() != Process.myUid()) { 204 throw new SecurityException("Only the owning process can manage slice permissions"); 205 } 206 mPermissionManager.revokeSlicePermission(uri, toPkg); 207 } 208 return null; 209 } 210 handleGetDescendants(Uri uri)211 private Collection<Uri> handleGetDescendants(Uri uri) { 212 mCallback = "onGetSliceDescendants"; 213 mHandler.postDelayed(mAnr, SLICE_BIND_ANR); 214 try { 215 return mProvider.onGetSliceDescendants(uri); 216 } finally { 217 mHandler.removeCallbacks(mAnr); 218 } 219 } 220 handleSlicePinned(final Uri sliceUri)221 private void handleSlicePinned(final Uri sliceUri) { 222 mCallback = "onSlicePinned"; 223 mHandler.postDelayed(mAnr, SLICE_BIND_ANR); 224 try { 225 mProvider.onSlicePinned(sliceUri); 226 } finally { 227 mHandler.removeCallbacks(mAnr); 228 } 229 } 230 handleSliceUnpinned(final Uri sliceUri)231 private void handleSliceUnpinned(final Uri sliceUri) { 232 mCallback = "onSliceUnpinned"; 233 mHandler.postDelayed(mAnr, SLICE_BIND_ANR); 234 try { 235 mProvider.onSliceUnpinned(sliceUri); 236 } finally { 237 mHandler.removeCallbacks(mAnr); 238 } 239 } 240 handleBindSlice(final Uri sliceUri, final Set<SliceSpec> specs, final String callingPkg)241 private Slice handleBindSlice(final Uri sliceUri, final Set<SliceSpec> specs, 242 final String callingPkg) { 243 // This can be removed once Slice#bindSlice is removed and everyone is using 244 // SliceManager#bindSlice. 245 String pkg = callingPkg != null ? callingPkg 246 : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); 247 if (mPermissionManager.checkSlicePermission(sliceUri, Binder.getCallingPid(), 248 Binder.getCallingUid()) != PERMISSION_GRANTED) { 249 return mProvider.createPermissionSlice(getContext(), sliceUri, pkg); 250 } 251 return onBindSliceStrict(sliceUri, specs); 252 } 253 onBindSliceStrict(Uri sliceUri, Set<SliceSpec> specs)254 private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec> specs) { 255 StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); 256 mCallback = "onBindSlice"; 257 mHandler.postDelayed(mAnr, SLICE_BIND_ANR); 258 try { 259 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 260 .detectAll() 261 .penaltyDeath() 262 .build()); 263 SliceProvider.setSpecs(specs); 264 try { 265 return mProvider.onBindSlice(sliceUri); 266 } finally { 267 SliceProvider.setSpecs(null); 268 mHandler.removeCallbacks(mAnr); 269 } 270 } finally { 271 StrictMode.setThreadPolicy(oldPolicy); 272 } 273 } 274 275 private final Runnable mAnr = new Runnable() { 276 @Override 277 public void run() { 278 Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); 279 Log.wtf(TAG, "Timed out while handling slice callback " + mCallback); 280 } 281 }; 282 283 /** 284 * Compat version of {@link Slice#bindSlice}. 285 */ bindSlice(Context context, Uri uri, Set<SliceSpec> supportedSpecs)286 public static Slice bindSlice(Context context, Uri uri, 287 Set<SliceSpec> supportedSpecs) { 288 ProviderHolder holder = acquireClient(context.getContentResolver(), uri); 289 if (holder.mProvider == null) { 290 throw new IllegalArgumentException("Unknown URI " + uri); 291 } 292 try { 293 Bundle extras = new Bundle(); 294 extras.putParcelable(EXTRA_BIND_URI, uri); 295 addSpecs(extras, supportedSpecs); 296 final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras); 297 if (res == null) { 298 return null; 299 } 300 Parcelable bundle = res.getParcelable(EXTRA_SLICE); 301 if (!(bundle instanceof Bundle)) { 302 return null; 303 } 304 return new Slice((Bundle) bundle); 305 } catch (RemoteException e) { 306 Log.e(TAG, "Unable to bind slice", e); 307 return null; 308 } finally { 309 } 310 } 311 312 /** 313 * Compat way to push specs through the call. 314 */ addSpecs(Bundle extras, Set<SliceSpec> supportedSpecs)315 public static void addSpecs(Bundle extras, Set<SliceSpec> supportedSpecs) { 316 ArrayList<String> types = new ArrayList<>(); 317 ArrayList<Integer> revs = new ArrayList<>(); 318 for (SliceSpec spec : supportedSpecs) { 319 types.add(spec.getType()); 320 revs.add(spec.getRevision()); 321 } 322 extras.putStringArrayList(EXTRA_SUPPORTED_SPECS, types); 323 extras.putIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS, revs); 324 } 325 326 /** 327 * Compat way to push specs through the call. 328 */ getSpecs(Bundle extras)329 public static Set<SliceSpec> getSpecs(Bundle extras) { 330 ArraySet<SliceSpec> specs = new ArraySet<>(); 331 ArrayList<String> types = extras.getStringArrayList(EXTRA_SUPPORTED_SPECS); 332 ArrayList<Integer> revs = extras.getIntegerArrayList(EXTRA_SUPPORTED_SPECS_REVS); 333 for (int i = 0; i < types.size(); i++) { 334 specs.add(new SliceSpec(types.get(i), revs.get(i))); 335 } 336 return specs; 337 } 338 339 /** 340 * Compat version of {@link Slice#bindSlice}. 341 */ bindSlice(Context context, Intent intent, Set<SliceSpec> supportedSpecs)342 public static Slice bindSlice(Context context, Intent intent, 343 Set<SliceSpec> supportedSpecs) { 344 Preconditions.checkNotNull(intent, "intent"); 345 Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null 346 || intent.getData() != null, 347 String.format("Slice intent must be explicit %s", intent)); 348 ContentResolver resolver = context.getContentResolver(); 349 350 // Check if the intent has data for the slice uri on it and use that 351 final Uri intentData = intent.getData(); 352 if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) { 353 return bindSlice(context, intentData, supportedSpecs); 354 } 355 // Otherwise ask the app 356 Intent queryIntent = new Intent(intent); 357 if (!queryIntent.hasCategory(CATEGORY_SLICE)) { 358 queryIntent.addCategory(CATEGORY_SLICE); 359 } 360 List<ResolveInfo> providers = 361 context.getPackageManager().queryIntentContentProviders(queryIntent, 0); 362 if (providers == null || providers.isEmpty()) { 363 // There are no providers, see if this activity has a direct link. 364 ResolveInfo resolve = context.getPackageManager().resolveActivity(intent, 365 PackageManager.GET_META_DATA); 366 if (resolve != null && resolve.activityInfo != null 367 && resolve.activityInfo.metaData != null 368 && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { 369 return bindSlice(context, Uri.parse( 370 resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)), 371 supportedSpecs); 372 } 373 return null; 374 } 375 String authority = providers.get(0).providerInfo.authority; 376 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 377 .authority(authority).build(); 378 ProviderHolder holder = acquireClient(resolver, uri); 379 if (holder.mProvider == null) { 380 throw new IllegalArgumentException("Unknown URI " + uri); 381 } 382 try { 383 Bundle extras = new Bundle(); 384 extras.putParcelable(EXTRA_INTENT, intent); 385 addSpecs(extras, supportedSpecs); 386 final Bundle res = holder.mProvider.call(METHOD_MAP_INTENT, null, extras); 387 if (res == null) { 388 return null; 389 } 390 Parcelable bundle = res.getParcelable(EXTRA_SLICE); 391 if (!(bundle instanceof Bundle)) { 392 return null; 393 } 394 return new Slice((Bundle) bundle); 395 } catch (RemoteException e) { 396 Log.e(TAG, "Unable to bind slice", e); 397 return null; 398 } 399 } 400 401 /** 402 * Compat version of {@link android.app.slice.SliceManager#pinSlice}. 403 */ pinSlice(Context context, Uri uri, Set<SliceSpec> supportedSpecs)404 public static void pinSlice(Context context, Uri uri, 405 Set<SliceSpec> supportedSpecs) { 406 ProviderHolder holder = acquireClient(context.getContentResolver(), uri); 407 if (holder.mProvider == null) { 408 throw new IllegalArgumentException("Unknown URI " + uri); 409 } 410 try { 411 Bundle extras = new Bundle(); 412 extras.putParcelable(EXTRA_BIND_URI, uri); 413 extras.putString(EXTRA_PKG, context.getPackageName()); 414 addSpecs(extras, supportedSpecs); 415 holder.mProvider.call(METHOD_PIN, null, extras); 416 } catch (RemoteException e) { 417 Log.e(TAG, "Unable to pin slice", e); 418 } 419 } 420 421 /** 422 * Compat version of {@link android.app.slice.SliceManager#unpinSlice}. 423 */ unpinSlice(Context context, Uri uri, Set<SliceSpec> supportedSpecs)424 public static void unpinSlice(Context context, Uri uri, 425 Set<SliceSpec> supportedSpecs) { 426 ProviderHolder holder = acquireClient(context.getContentResolver(), uri); 427 if (holder.mProvider == null) { 428 throw new IllegalArgumentException("Unknown URI " + uri); 429 } 430 try { 431 Bundle extras = new Bundle(); 432 extras.putParcelable(EXTRA_BIND_URI, uri); 433 extras.putString(EXTRA_PKG, context.getPackageName()); 434 addSpecs(extras, supportedSpecs); 435 holder.mProvider.call(METHOD_UNPIN, null, extras); 436 } catch (RemoteException e) { 437 Log.e(TAG, "Unable to unpin slice", e); 438 } 439 } 440 441 /** 442 * Compat version of {@link android.app.slice.SliceManager#getPinnedSpecs(Uri)}. 443 */ getPinnedSpecs(Context context, Uri uri)444 public static Set<SliceSpec> getPinnedSpecs(Context context, Uri uri) { 445 ProviderHolder holder = acquireClient(context.getContentResolver(), uri); 446 if (holder.mProvider == null) { 447 throw new IllegalArgumentException("Unknown URI " + uri); 448 } 449 try { 450 Bundle extras = new Bundle(); 451 extras.putParcelable(EXTRA_BIND_URI, uri); 452 final Bundle res = holder.mProvider.call(METHOD_GET_PINNED_SPECS, null, extras); 453 if (res != null) { 454 return getSpecs(res); 455 } 456 } catch (RemoteException e) { 457 Log.e(TAG, "Unable to get pinned specs", e); 458 } 459 return null; 460 } 461 462 /** 463 * Compat version of {@link android.app.slice.SliceManager#mapIntentToUri}. 464 */ mapIntentToUri(Context context, Intent intent)465 public static Uri mapIntentToUri(Context context, Intent intent) { 466 Preconditions.checkNotNull(intent, "intent"); 467 Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null 468 || intent.getData() != null, 469 String.format("Slice intent must be explicit %s", intent)); 470 ContentResolver resolver = context.getContentResolver(); 471 472 // Check if the intent has data for the slice uri on it and use that 473 final Uri intentData = intent.getData(); 474 if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) { 475 return intentData; 476 } 477 // Otherwise ask the app 478 Intent queryIntent = new Intent(intent); 479 if (!queryIntent.hasCategory(CATEGORY_SLICE)) { 480 queryIntent.addCategory(CATEGORY_SLICE); 481 } 482 List<ResolveInfo> providers = 483 context.getPackageManager().queryIntentContentProviders(queryIntent, 0); 484 if (providers == null || providers.isEmpty()) { 485 // There are no providers, see if this activity has a direct link. 486 ResolveInfo resolve = context.getPackageManager().resolveActivity(intent, 487 PackageManager.GET_META_DATA); 488 if (resolve != null && resolve.activityInfo != null 489 && resolve.activityInfo.metaData != null 490 && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) { 491 return Uri.parse( 492 resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY)); 493 } 494 return null; 495 } 496 String authority = providers.get(0).providerInfo.authority; 497 Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 498 .authority(authority).build(); 499 try (ProviderHolder holder = acquireClient(resolver, uri)) { 500 if (holder.mProvider == null) { 501 throw new IllegalArgumentException("Unknown URI " + uri); 502 } 503 Bundle extras = new Bundle(); 504 extras.putParcelable(EXTRA_INTENT, intent); 505 final Bundle res = holder.mProvider.call(METHOD_MAP_ONLY_INTENT, null, extras); 506 if (res != null) { 507 return res.getParcelable(EXTRA_SLICE); 508 } 509 } catch (RemoteException e) { 510 Log.e(TAG, "Unable to map slice", e); 511 } 512 return null; 513 } 514 515 /** 516 * Compat version of {@link android.app.slice.SliceManager#getSliceDescendants(Uri)} 517 */ getSliceDescendants(Context context, @NonNull Uri uri)518 public static @NonNull Collection<Uri> getSliceDescendants(Context context, @NonNull Uri uri) { 519 ContentResolver resolver = context.getContentResolver(); 520 try (ProviderHolder holder = acquireClient(resolver, uri)) { 521 Bundle extras = new Bundle(); 522 extras.putParcelable(EXTRA_BIND_URI, uri); 523 final Bundle res = holder.mProvider.call(METHOD_GET_DESCENDANTS, null, extras); 524 if (res != null) { 525 return res.getParcelableArrayList(EXTRA_SLICE_DESCENDANTS); 526 } 527 } catch (RemoteException e) { 528 Log.e(TAG, "Unable to get slice descendants", e); 529 } 530 return Collections.emptyList(); 531 } 532 533 /** 534 * Compat version of {@link android.app.slice.SliceManager#checkSlicePermission}. 535 */ checkSlicePermission(Context context, String packageName, Uri uri, int pid, int uid)536 public static int checkSlicePermission(Context context, String packageName, Uri uri, int pid, 537 int uid) { 538 ContentResolver resolver = context.getContentResolver(); 539 try (ProviderHolder holder = acquireClient(resolver, uri)) { 540 Bundle extras = new Bundle(); 541 extras.putParcelable(EXTRA_BIND_URI, uri); 542 extras.putString(EXTRA_PKG, packageName); 543 extras.putInt(EXTRA_PID, pid); 544 extras.putInt(EXTRA_UID, uid); 545 546 final Bundle res = holder.mProvider.call(METHOD_CHECK_PERMISSION, null, extras); 547 if (res != null) { 548 return res.getInt(EXTRA_RESULT); 549 } 550 } catch (RemoteException e) { 551 Log.e(TAG, "Unable to check slice permission", e); 552 } 553 return PERMISSION_DENIED; 554 } 555 556 /** 557 * Compat version of {@link android.app.slice.SliceManager#grantSlicePermission}. 558 */ grantSlicePermission(Context context, String packageName, String toPackage, Uri uri)559 public static void grantSlicePermission(Context context, String packageName, String toPackage, 560 Uri uri) { 561 ContentResolver resolver = context.getContentResolver(); 562 try (ProviderHolder holder = acquireClient(resolver, uri)) { 563 Bundle extras = new Bundle(); 564 extras.putParcelable(EXTRA_BIND_URI, uri); 565 extras.putString(EXTRA_PROVIDER_PKG, packageName); 566 extras.putString(EXTRA_PKG, toPackage); 567 568 holder.mProvider.call(METHOD_GRANT_PERMISSION, null, extras); 569 } catch (RemoteException e) { 570 Log.e(TAG, "Unable to get slice descendants", e); 571 } 572 } 573 574 /** 575 * Compat version of {@link android.app.slice.SliceManager#revokeSlicePermission}. 576 */ revokeSlicePermission(Context context, String packageName, String toPackage, Uri uri)577 public static void revokeSlicePermission(Context context, String packageName, String toPackage, 578 Uri uri) { 579 ContentResolver resolver = context.getContentResolver(); 580 try (ProviderHolder holder = acquireClient(resolver, uri)) { 581 Bundle extras = new Bundle(); 582 extras.putParcelable(EXTRA_BIND_URI, uri); 583 extras.putString(EXTRA_PROVIDER_PKG, packageName); 584 extras.putString(EXTRA_PKG, toPackage); 585 586 holder.mProvider.call(METHOD_REVOKE_PERMISSION, null, extras); 587 } catch (RemoteException e) { 588 Log.e(TAG, "Unable to get slice descendants", e); 589 } 590 } 591 592 /** 593 * Compat version of {@link android.app.slice.SliceManager#getPinnedSlices}. 594 */ getPinnedSlices(Context context)595 public static List<Uri> getPinnedSlices(Context context) { 596 ArrayList<Uri> pinnedSlices = new ArrayList<>(); 597 SharedPreferences prefs = context.getSharedPreferences(ALL_FILES, 0); 598 Set<String> prefSet = prefs.getStringSet(ALL_FILES, Collections.<String>emptySet()); 599 for (String pref : prefSet) { 600 pinnedSlices.addAll(new CompatPinnedList(context, pref).getPinnedSlices()); 601 } 602 return pinnedSlices; 603 } 604 acquireClient(ContentResolver resolver, Uri uri)605 private static ProviderHolder acquireClient(ContentResolver resolver, Uri uri) { 606 ContentProviderClient provider = resolver.acquireContentProviderClient(uri); 607 if (provider == null) { 608 throw new IllegalArgumentException("No provider found for " + uri); 609 } 610 return new ProviderHolder(provider); 611 } 612 613 private static class ProviderHolder implements AutoCloseable { 614 private final ContentProviderClient mProvider; 615 ProviderHolder(ContentProviderClient provider)616 ProviderHolder(ContentProviderClient provider) { 617 this.mProvider = provider; 618 } 619 620 @Override close()621 public void close() { 622 if (mProvider == null) return; 623 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 624 mProvider.close(); 625 } else { 626 mProvider.release(); 627 } 628 } 629 } 630 } 631