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 
17 package android.app.slice;
18 
19 import static android.content.pm.PackageManager.PERMISSION_DENIED;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SdkConstant;
24 import android.annotation.SdkConstant.SdkConstantType;
25 import android.annotation.SystemService;
26 import android.annotation.WorkerThread;
27 import android.content.ContentProviderClient;
28 import android.content.ContentResolver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.pm.PackageManager;
32 import android.content.pm.PackageManager.PermissionResult;
33 import android.content.pm.ResolveInfo;
34 import android.net.Uri;
35 import android.os.Binder;
36 import android.os.Bundle;
37 import android.os.Handler;
38 import android.os.IBinder;
39 import android.os.Process;
40 import android.os.RemoteException;
41 import android.os.ServiceManager;
42 import android.os.ServiceManager.ServiceNotFoundException;
43 import android.os.UserHandle;
44 import android.text.TextUtils;
45 import android.util.ArraySet;
46 import android.util.Log;
47 
48 import com.android.internal.util.Preconditions;
49 
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collection;
53 import java.util.Collections;
54 import java.util.List;
55 import java.util.Objects;
56 import java.util.Set;
57 
58 /**
59  * Class to handle interactions with {@link Slice}s.
60  * <p>
61  * The SliceManager manages permissions and pinned state for slices.
62  * @deprecated Slice framework has been deprecated, it will not receive any updates from
63  *          {@link android.os.Build.VANILLA_ICE_CREAM} and forward. If you are looking for a
64  *          framework that sends displayable data from one app to another, consider using
65  *          {@link android.app.appsearch.AppSearchManager}.
66  */
67 @Deprecated
68 @SystemService(Context.SLICE_SERVICE)
69 public class SliceManager {
70 
71     private static final String TAG = "SliceManager";
72 
73     /**
74      * @hide
75      */
76     public static final String ACTION_REQUEST_SLICE_PERMISSION =
77             "com.android.intent.action.REQUEST_SLICE_PERMISSION";
78 
79     /**
80      * Category used to resolve intents that can be rendered as slices.
81      * <p>
82      * This category should be included on intent filters on providers that extend
83      * {@link SliceProvider}.
84      * @see SliceProvider
85      * @see SliceProvider#onMapIntentToUri(Intent)
86      * @see #mapIntentToUri(Intent)
87      */
88     @SdkConstant(SdkConstantType.INTENT_CATEGORY)
89     public static final String CATEGORY_SLICE = "android.app.slice.category.SLICE";
90 
91     /**
92      * The meta-data key that allows an activity to easily be linked directly to a slice.
93      * <p>
94      * An activity can be statically linked to a slice uri by including a meta-data item
95      * for this key that contains a valid slice uri for the same application declaring
96      * the activity.
97      *
98      * <pre class="prettyprint">
99      * {@literal
100      * <activity android:name="com.example.mypkg.MyActivity">
101      *     <meta-data android:name="android.metadata.SLICE_URI"
102      *                android:value="content://com.example.mypkg/main_slice" />
103      *  </activity>}
104      * </pre>
105      *
106      * @see #mapIntentToUri(Intent)
107      * @see SliceProvider#onMapIntentToUri(Intent)
108      */
109     public static final String SLICE_METADATA_KEY = "android.metadata.SLICE_URI";
110 
111     private final ISliceManager mService;
112     private final Context mContext;
113     private final IBinder mToken = new Binder();
114 
115     /**
116      * @hide
117      */
SliceManager(Context context, Handler handler)118     public SliceManager(Context context, Handler handler) throws ServiceNotFoundException {
119         mContext = context;
120         mService = ISliceManager.Stub.asInterface(
121                 ServiceManager.getServiceOrThrow(Context.SLICE_SERVICE));
122     }
123 
124     /**
125      * Ensures that a slice is in a pinned state.
126      * <p>
127      * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices
128      * they still care about after a reboot.
129      * <p>
130      * This may only be called by apps that are the default launcher for the device
131      * or the default voice interaction service. Otherwise will throw {@link SecurityException}.
132      *
133      * @param uri The uri of the slice being pinned.
134      * @param specs The list of supported {@link SliceSpec}s of the callback.
135      * @see SliceProvider#onSlicePinned(Uri)
136      * @see Intent#ACTION_ASSIST
137      * @see Intent#CATEGORY_HOME
138      */
pinSlice(@onNull Uri uri, @NonNull Set<SliceSpec> specs)139     public void pinSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> specs) {
140         try {
141             mService.pinSlice(mContext.getPackageName(), uri,
142                     specs.toArray(new SliceSpec[specs.size()]), mToken);
143         } catch (RemoteException e) {
144             throw e.rethrowFromSystemServer();
145         }
146     }
147 
148     /**
149      * Remove a pin for a slice.
150      * <p>
151      * If the slice has no other pins/callbacks then the slice will be unpinned.
152      * <p>
153      * This may only be called by apps that are the default launcher for the device
154      * or the default voice interaction service. Otherwise will throw {@link SecurityException}.
155      *
156      * @param uri The uri of the slice being unpinned.
157      * @see #pinSlice
158      * @see SliceProvider#onSliceUnpinned(Uri)
159      * @see Intent#ACTION_ASSIST
160      * @see Intent#CATEGORY_HOME
161      */
unpinSlice(@onNull Uri uri)162     public void unpinSlice(@NonNull Uri uri) {
163         try {
164             mService.unpinSlice(mContext.getPackageName(), uri, mToken);
165         } catch (RemoteException e) {
166             throw e.rethrowFromSystemServer();
167         }
168     }
169 
170     /**
171      * @hide
172      */
hasSliceAccess()173     public boolean hasSliceAccess() {
174         try {
175             return mService.hasSliceAccess(mContext.getPackageName());
176         } catch (RemoteException e) {
177             throw e.rethrowFromSystemServer();
178         }
179     }
180 
181     /**
182      * Get the current set of specs for a pinned slice.
183      * <p>
184      * This is the set of specs supported for a specific pinned slice. It will take
185      * into account all clients and returns only specs supported by all.
186      * @see SliceSpec
187      */
getPinnedSpecs(Uri uri)188     public @NonNull Set<SliceSpec> getPinnedSpecs(Uri uri) {
189         try {
190             return new ArraySet<>(Arrays.asList(mService.getPinnedSpecs(uri,
191                     mContext.getPackageName())));
192         } catch (RemoteException e) {
193             throw e.rethrowFromSystemServer();
194         }
195     }
196 
197     /**
198      * Get the list of currently pinned slices for this app.
199      * @see SliceProvider#onSlicePinned
200      */
getPinnedSlices()201     public @NonNull List<Uri> getPinnedSlices() {
202         try {
203             return Arrays.asList(mService.getPinnedSlices(mContext.getPackageName()));
204         } catch (RemoteException e) {
205             throw e.rethrowFromSystemServer();
206         }
207     }
208 
209     /**
210      * Obtains a list of slices that are descendants of the specified Uri.
211      * <p>
212      * Not all slice providers will implement this functionality, in which case,
213      * an empty collection will be returned.
214      *
215      * @param uri The uri to look for descendants under.
216      * @return All slices within the space.
217      * @see SliceProvider#onGetSliceDescendants(Uri)
218      */
219     @WorkerThread
getSliceDescendants(@onNull Uri uri)220     public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) {
221         ContentResolver resolver = mContext.getContentResolver();
222         try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
223             if (provider == null) {
224                 Log.w(TAG, TextUtils.formatSimple("Unknown URI: %s", uri));
225             } else {
226                 Bundle extras = new Bundle();
227                 extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
228                 final Bundle res = provider.call(
229                         SliceProvider.METHOD_GET_DESCENDANTS, null, extras);
230                 return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS, android.net.Uri.class);
231             }
232         } catch (RemoteException e) {
233             Log.e(TAG, "Unable to get slice descendants", e);
234         }
235         return Collections.emptyList();
236     }
237 
238     /**
239      * Turns a slice Uri into slice content.
240      *
241      * @param uri The URI to a slice provider
242      * @param supportedSpecs List of supported specs.
243      * @return The Slice provided by the app or null if none is given.
244      * @see Slice
245      */
bindSlice(@onNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs)246     public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull Set<SliceSpec> supportedSpecs) {
247         Objects.requireNonNull(uri, "uri");
248         ContentResolver resolver = mContext.getContentResolver();
249         try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
250             if (provider == null) {
251                 Log.w(TAG, String.format("Unknown URI: %s", uri));
252                 return null;
253             }
254             Bundle extras = new Bundle();
255             extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri);
256             extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
257                     new ArrayList<>(supportedSpecs));
258             final Bundle res = provider.call(SliceProvider.METHOD_SLICE, null, extras);
259             Bundle.setDefusable(res, true);
260             if (res == null) {
261                 return null;
262             }
263             return res.getParcelable(SliceProvider.EXTRA_SLICE, android.app.slice.Slice.class);
264         } catch (RemoteException e) {
265             // Arbitrary and not worth documenting, as Activity
266             // Manager will kill this process shortly anyway.
267             return null;
268         }
269     }
270 
271     /**
272      * Turns a slice intent into a slice uri. Expects an explicit intent.
273      * <p>
274      * This goes through a several stage resolution process to determine if any slice
275      * can represent this intent.
276      * <ol>
277      *  <li> If the intent contains data that {@link ContentResolver#getType} is
278      *  {@link SliceProvider#SLICE_TYPE} then the data will be returned.</li>
279      *  <li>If the intent explicitly points at an activity, and that activity has
280      *  meta-data for key {@link #SLICE_METADATA_KEY}, then the Uri specified there will be
281      *  returned.</li>
282      *  <li>Lastly, if the intent with {@link #CATEGORY_SLICE} added resolves to a provider, then
283      *  the provider will be asked to {@link SliceProvider#onMapIntentToUri} and that result
284      *  will be returned.</li>
285      *  <li>If no slice is found, then {@code null} is returned.</li>
286      * </ol>
287      * @param intent The intent associated with a slice.
288      * @return The Slice Uri provided by the app or null if none exists.
289      * @see Slice
290      * @see SliceProvider#onMapIntentToUri(Intent)
291      * @see Intent
292      */
mapIntentToUri(@onNull Intent intent)293     public @Nullable Uri mapIntentToUri(@NonNull Intent intent) {
294         ContentResolver resolver = mContext.getContentResolver();
295         final Uri staticUri = resolveStatic(intent, resolver);
296         if (staticUri != null) return staticUri;
297         // Otherwise ask the app
298         String authority = getAuthority(intent);
299         if (authority == null) return null;
300         Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
301                 .authority(authority).build();
302         try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
303             if (provider == null) {
304                 Log.w(TAG, String.format("Unknown URI: %s", uri));
305                 return null;
306             }
307             Bundle extras = new Bundle();
308             extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
309             final Bundle res = provider.call(SliceProvider.METHOD_MAP_ONLY_INTENT, null, extras);
310             if (res == null) {
311                 return null;
312             }
313             return res.getParcelable(SliceProvider.EXTRA_SLICE, android.net.Uri.class);
314         } catch (RemoteException e) {
315             // Arbitrary and not worth documenting, as Activity
316             // Manager will kill this process shortly anyway.
317             return null;
318         }
319     }
320 
getAuthority(Intent intent)321     private String getAuthority(Intent intent) {
322         Intent queryIntent = new Intent(intent);
323         if (!queryIntent.hasCategory(CATEGORY_SLICE)) {
324             queryIntent.addCategory(CATEGORY_SLICE);
325         }
326         List<ResolveInfo> providers =
327                 mContext.getPackageManager().queryIntentContentProviders(queryIntent, 0);
328         return providers != null && !providers.isEmpty() ? providers.get(0).providerInfo.authority
329                 : null;
330     }
331 
resolveStatic(@onNull Intent intent, ContentResolver resolver)332     private Uri resolveStatic(@NonNull Intent intent, ContentResolver resolver) {
333         Objects.requireNonNull(intent, "intent");
334         Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
335                 || intent.getData() != null,
336                 "Slice intent must be explicit %s", intent);
337 
338         // Check if the intent has data for the slice uri on it and use that
339         final Uri intentData = intent.getData();
340         if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) {
341             return intentData;
342         }
343         // There are no providers, see if this activity has a direct link.
344         ResolveInfo resolve = mContext.getPackageManager().resolveActivity(intent,
345                 PackageManager.GET_META_DATA);
346         if (resolve != null && resolve.activityInfo != null
347                 && resolve.activityInfo.metaData != null
348                 && resolve.activityInfo.metaData.containsKey(SLICE_METADATA_KEY)) {
349             return Uri.parse(
350                     resolve.activityInfo.metaData.getString(SLICE_METADATA_KEY));
351         }
352         return null;
353     }
354 
355     /**
356      * Turns a slice intent into slice content. Is a shortcut to perform the action
357      * of both {@link #mapIntentToUri(Intent)} and {@link #bindSlice(Uri, Set)} at once.
358      *
359      * @param intent The intent associated with a slice.
360      * @param supportedSpecs List of supported specs.
361      * @return The Slice provided by the app or null if none is given.
362      * @see Slice
363      * @see SliceProvider#onMapIntentToUri(Intent)
364      * @see Intent
365      */
bindSlice(@onNull Intent intent, @NonNull Set<SliceSpec> supportedSpecs)366     public @Nullable Slice bindSlice(@NonNull Intent intent,
367             @NonNull Set<SliceSpec> supportedSpecs) {
368         Objects.requireNonNull(intent, "intent");
369         Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null
370                 || intent.getData() != null,
371                 "Slice intent must be explicit %s", intent);
372         ContentResolver resolver = mContext.getContentResolver();
373         final Uri staticUri = resolveStatic(intent, resolver);
374         if (staticUri != null) return bindSlice(staticUri, supportedSpecs);
375         // Otherwise ask the app
376         String authority = getAuthority(intent);
377         if (authority == null) return null;
378         Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
379                 .authority(authority).build();
380         try (ContentProviderClient provider = resolver.acquireUnstableContentProviderClient(uri)) {
381             if (provider == null) {
382                 Log.w(TAG, String.format("Unknown URI: %s", uri));
383                 return null;
384             }
385             Bundle extras = new Bundle();
386             extras.putParcelable(SliceProvider.EXTRA_INTENT, intent);
387             extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS,
388                     new ArrayList<>(supportedSpecs));
389             final Bundle res = provider.call(SliceProvider.METHOD_MAP_INTENT, null, extras);
390             if (res == null) {
391                 return null;
392             }
393             return res.getParcelable(SliceProvider.EXTRA_SLICE, android.app.slice.Slice.class);
394         } catch (RemoteException e) {
395             // Arbitrary and not worth documenting, as Activity
396             // Manager will kill this process shortly anyway.
397             return null;
398         }
399     }
400 
401     /**
402      * Determine whether a particular process and user ID has been granted
403      * permission to access a specific slice URI.
404      *
405      * @param uri The uri that is being checked.
406      * @param pid The process ID being checked against.  Must be &gt; 0.
407      * @param uid The user ID being checked against.  A uid of 0 is the root
408      * user, which will pass every permission check.
409      *
410      * @return {@link PackageManager#PERMISSION_GRANTED} if the given
411      * pid/uid is allowed to access that uri, or
412      * {@link PackageManager#PERMISSION_DENIED} if it is not.
413      *
414      * @see #grantSlicePermission(String, Uri)
415      */
checkSlicePermission(@onNull Uri uri, int pid, int uid)416     public @PermissionResult int checkSlicePermission(@NonNull Uri uri, int pid, int uid) {
417         try {
418             return mService.checkSlicePermission(uri, mContext.getPackageName(), pid, uid,
419                     null /* autoGrantPermissions */);
420         } catch (RemoteException e) {
421             throw e.rethrowFromSystemServer();
422         }
423     }
424 
425     /**
426      * Grant permission to access a specific slice Uri to another package.
427      *
428      * @param toPackage The package you would like to allow to access the Uri.
429      * @param uri The Uri you would like to grant access to.
430      *
431      * @see #revokeSlicePermission
432      */
grantSlicePermission(@onNull String toPackage, @NonNull Uri uri)433     public void grantSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
434         try {
435             mService.grantSlicePermission(mContext.getPackageName(), toPackage, uri);
436         } catch (RemoteException e) {
437             throw e.rethrowFromSystemServer();
438         }
439     }
440 
441     /**
442      * Remove permissions to access a particular content provider Uri
443      * that were previously added with {@link #grantSlicePermission} for a specific target
444      * package.  The given Uri will match all previously granted Uris that are the same or a
445      * sub-path of the given Uri.  That is, revoking "content://foo/target" will
446      * revoke both "content://foo/target" and "content://foo/target/sub", but not
447      * "content://foo".  It will not remove any prefix grants that exist at a
448      * higher level.
449      *
450      * @param toPackage The package you would like to allow to access the Uri.
451      * @param uri The Uri you would like to revoke access to.
452      *
453      * @see #grantSlicePermission
454      */
revokeSlicePermission(@onNull String toPackage, @NonNull Uri uri)455     public void revokeSlicePermission(@NonNull String toPackage, @NonNull Uri uri) {
456         try {
457             mService.revokeSlicePermission(mContext.getPackageName(), toPackage, uri);
458         } catch (RemoteException e) {
459             throw e.rethrowFromSystemServer();
460         }
461     }
462 
463     /**
464      * Does the permission check to see if a caller has access to a specific slice.
465      * @hide
466      */
enforceSlicePermission(Uri uri, int pid, int uid, String[] autoGrantPermissions)467     public void enforceSlicePermission(Uri uri, int pid, int uid, String[] autoGrantPermissions) {
468         try {
469             if (UserHandle.isSameApp(uid, Process.myUid())) {
470                 return;
471             }
472             int result = mService.checkSlicePermission(uri, mContext.getPackageName(), pid, uid,
473                     autoGrantPermissions);
474             if (result == PERMISSION_DENIED) {
475                 throw new SecurityException("User " + uid + " does not have slice permission for "
476                         + uri + ".");
477             }
478         } catch (RemoteException e) {
479             throw e.rethrowFromSystemServer();
480         }
481     }
482 
483     /**
484      * Called by SystemUI to grant a slice permission after a dialog is shown.
485      * @hide
486      */
grantPermissionFromUser(Uri uri, String pkg, boolean allSlices)487     public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) {
488         try {
489             mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices);
490         } catch (RemoteException e) {
491             throw e.rethrowFromSystemServer();
492         }
493     }
494 }
495