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