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