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