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