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