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