diff options
Diffstat (limited to 'androidx/slice/SliceProvider.java')
-rw-r--r-- | androidx/slice/SliceProvider.java | 359 |
1 files changed, 345 insertions, 14 deletions
diff --git a/androidx/slice/SliceProvider.java b/androidx/slice/SliceProvider.java index 51ffea6b..7ec9232b 100644 --- a/androidx/slice/SliceProvider.java +++ b/androidx/slice/SliceProvider.java @@ -15,22 +15,61 @@ */ package androidx.slice; +import static android.app.slice.Slice.HINT_SHORTCUT; +import static android.app.slice.Slice.HINT_TITLE; +import static android.app.slice.SliceProvider.SLICE_TYPE; + +import static androidx.slice.compat.SliceProviderCompat.EXTRA_BIND_URI; +import static androidx.slice.compat.SliceProviderCompat.EXTRA_INTENT; +import static androidx.slice.compat.SliceProviderCompat.EXTRA_PKG; +import static androidx.slice.compat.SliceProviderCompat.EXTRA_PROVIDER_PKG; +import static androidx.slice.compat.SliceProviderCompat.EXTRA_SLICE; +import static androidx.slice.compat.SliceProviderCompat.EXTRA_SLICE_DESCENDANTS; +import static androidx.slice.compat.SliceProviderCompat.METHOD_GET_DESCENDANTS; +import static androidx.slice.compat.SliceProviderCompat.METHOD_GET_PINNED_SPECS; +import static androidx.slice.compat.SliceProviderCompat.METHOD_MAP_INTENT; +import static androidx.slice.compat.SliceProviderCompat.METHOD_MAP_ONLY_INTENT; +import static androidx.slice.compat.SliceProviderCompat.METHOD_PIN; +import static androidx.slice.compat.SliceProviderCompat.METHOD_SLICE; +import static androidx.slice.compat.SliceProviderCompat.METHOD_UNPIN; +import static androidx.slice.compat.SliceProviderCompat.addSpecs; +import static androidx.slice.compat.SliceProviderCompat.getSpecs; +import static androidx.slice.core.SliceHints.HINT_PERMISSION_REQUEST; + +import android.app.PendingIntent; +import android.content.ComponentName; import android.content.ContentProvider; import android.content.ContentResolver; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ProviderInfo; +import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.database.Cursor; import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; +import android.os.Process; +import android.os.StrictMode; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.RestrictTo; +import androidx.core.app.CoreComponentFactory; import androidx.core.os.BuildCompat; -import androidx.slice.compat.ContentProviderWrapper; -import androidx.slice.compat.SliceProviderCompat; +import androidx.slice.compat.CompatPinnedList; import androidx.slice.compat.SliceProviderWrapperContainer; +import androidx.slice.core.R; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.Set; /** @@ -72,20 +111,21 @@ import java.util.Set; * * @see android.app.slice.Slice */ -public abstract class SliceProvider extends ContentProviderWrapper { +public abstract class SliceProvider extends ContentProvider implements + CoreComponentFactory.CompatWrapped { private static Set<SliceSpec> sSpecs; - @Override - public void attachInfo(Context context, ProviderInfo info) { - ContentProvider impl; - if (BuildCompat.isAtLeastP()) { - impl = new SliceProviderWrapperContainer.SliceProviderWrapper(this); - } else { - impl = new SliceProviderCompat(this); - } - super.attachInfo(context, info, impl); - } + private static final String TAG = "SliceProvider"; + + private static final String DATA_PREFIX = "slice_data_"; + private static final long SLICE_BIND_ANR = 2000; + + private static final boolean DEBUG = false; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private CompatPinnedList mPinnedList; + + private String mCallback; /** * Implement this to initialize your slice provider on startup. @@ -104,6 +144,228 @@ public abstract class SliceProvider extends ContentProviderWrapper { */ public abstract boolean onCreateSliceProvider(); + @Override + public Object getWrapper() { + if (BuildCompat.isAtLeastP()) { + return new SliceProviderWrapperContainer.SliceProviderWrapper(this); + } + return null; + } + + @Override + public final boolean onCreate() { + mPinnedList = new CompatPinnedList(getContext(), + DATA_PREFIX + getClass().getName()); + return onCreateSliceProvider(); + } + + @Override + public final String getType(Uri uri) { + if (DEBUG) Log.d(TAG, "getFormat " + uri); + return SLICE_TYPE; + } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (method.equals(METHOD_SLICE)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + if (Binder.getCallingUid() != Process.myUid()) { + getContext().enforceUriPermission(uri, Binder.getCallingPid(), + Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires write access to the uri"); + } + Set<SliceSpec> specs = getSpecs(extras); + + Slice s = handleBindSlice(uri, specs, getCallingPackage()); + Bundle b = new Bundle(); + b.putParcelable(EXTRA_SLICE, s.toBundle()); + return b; + } else if (method.equals(METHOD_MAP_INTENT)) { + Intent intent = extras.getParcelable(EXTRA_INTENT); + Uri uri = onMapIntentToUri(intent); + Bundle b = new Bundle(); + if (uri != null) { + Set<SliceSpec> specs = getSpecs(extras); + Slice s = handleBindSlice(uri, specs, getCallingPackage()); + b.putParcelable(EXTRA_SLICE, s.toBundle()); + } else { + b.putParcelable(EXTRA_SLICE, null); + } + return b; + } else if (method.equals(METHOD_MAP_ONLY_INTENT)) { + Intent intent = extras.getParcelable(EXTRA_INTENT); + Uri uri = onMapIntentToUri(intent); + Bundle b = new Bundle(); + b.putParcelable(EXTRA_SLICE, uri); + return b; + } else if (method.equals(METHOD_PIN)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Set<SliceSpec> specs = getSpecs(extras); + String pkg = extras.getString(EXTRA_PKG); + if (mPinnedList.addPin(uri, pkg, specs)) { + handleSlicePinned(uri); + } + return null; + } else if (method.equals(METHOD_UNPIN)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + String pkg = extras.getString(EXTRA_PKG); + if (mPinnedList.removePin(uri, pkg)) { + handleSliceUnpinned(uri); + } + return null; + } else if (method.equals(METHOD_GET_PINNED_SPECS)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Bundle b = new Bundle(); + addSpecs(b, mPinnedList.getSpecs(uri)); + return b; + } else if (method.equals(METHOD_GET_DESCENDANTS)) { + Uri uri = extras.getParcelable(EXTRA_BIND_URI); + Bundle b = new Bundle(); + b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS, + new ArrayList<>(handleGetDescendants(uri))); + return b; + } + return super.call(method, arg, extras); + } + + private Collection<Uri> handleGetDescendants(Uri uri) { + mCallback = "onGetSliceDescendants"; + mHandler.postDelayed(mAnr, SLICE_BIND_ANR); + try { + return onGetSliceDescendants(uri); + } finally { + mHandler.removeCallbacks(mAnr); + } + } + + private void handleSlicePinned(final Uri sliceUri) { + mCallback = "onSlicePinned"; + mHandler.postDelayed(mAnr, SLICE_BIND_ANR); + try { + onSlicePinned(sliceUri); + } finally { + mHandler.removeCallbacks(mAnr); + } + } + + private void handleSliceUnpinned(final Uri sliceUri) { + mCallback = "onSliceUnpinned"; + mHandler.postDelayed(mAnr, SLICE_BIND_ANR); + try { + onSliceUnpinned(sliceUri); + } finally { + mHandler.removeCallbacks(mAnr); + } + } + + private Slice handleBindSlice(final Uri sliceUri, final Set<SliceSpec> specs, + final String callingPkg) { + // This can be removed once Slice#bindSlice is removed and everyone is using + // SliceManager#bindSlice. + String pkg = callingPkg != null ? callingPkg + : getContext().getPackageManager().getNameForUid(Binder.getCallingUid()); + if (Binder.getCallingUid() != Process.myUid()) { + try { + getContext().enforceUriPermission(sliceUri, + Binder.getCallingPid(), Binder.getCallingUid(), + Intent.FLAG_GRANT_WRITE_URI_PERMISSION, + "Slice binding requires write access to Uri"); + } catch (SecurityException e) { + return createPermissionSlice(getContext(), sliceUri, pkg); + } + } + return onBindSliceStrict(sliceUri, specs); + } + + /** + * Generate a slice that contains a permission request. + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public static Slice createPermissionSlice(Context context, Uri sliceUri, + String callingPackage) { + Slice.Builder parent = new Slice.Builder(sliceUri); + + Slice.Builder action = new Slice.Builder(parent) + .addHints(HINT_TITLE, HINT_SHORTCUT) + .addAction(createPermissionIntent(context, sliceUri, callingPackage), + new Slice.Builder(parent).build(), null); + + parent.addSubSlice(new Slice.Builder(sliceUri.buildUpon().appendPath("permission").build()) + .addText(getPermissionString(context, callingPackage), null) + .addSubSlice(action.build()) + .build()); + + return parent.addHints(HINT_PERMISSION_REQUEST).build(); + } + + /** + * Create a PendingIntent pointing at the permission dialog. + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public static PendingIntent createPermissionIntent(Context context, Uri sliceUri, + String callingPackage) { + Intent intent = new Intent(); + intent.setComponent(new ComponentName(context.getPackageName(), + "androidx.slice.compat.SlicePermissionActivity")); + intent.putExtra(EXTRA_BIND_URI, sliceUri); + intent.putExtra(EXTRA_PKG, callingPackage); + intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName()); + // Unique pending intent. + intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage) + .build()); + + return PendingIntent.getActivity(context, 0, intent, 0); + } + + /** + * Get string describing permission request. + * @hide + */ + @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) + public static CharSequence getPermissionString(Context context, String callingPackage) { + PackageManager pm = context.getPackageManager(); + try { + return context.getString(R.string.abc_slices_permission_request, + pm.getApplicationInfo(callingPackage, 0).loadLabel(pm), + context.getApplicationInfo().loadLabel(pm)); + } catch (PackageManager.NameNotFoundException e) { + // This shouldn't be possible since the caller is verified. + throw new RuntimeException("Unknown calling app", e); + } + } + + private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec> specs) { + StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy(); + mCallback = "onBindSlice"; + mHandler.postDelayed(mAnr, SLICE_BIND_ANR); + try { + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectAll() + .penaltyDeath() + .build()); + SliceProvider.setSpecs(specs); + try { + return onBindSlice(sliceUri); + } finally { + SliceProvider.setSpecs(null); + mHandler.removeCallbacks(mAnr); + } + } finally { + StrictMode.setThreadPolicy(oldPolicy); + } + } + + private final Runnable mAnr = new Runnable() { + @Override + public void run() { + Process.sendSignal(Process.myPid(), Process.SIGNAL_QUIT); + Log.wtf(TAG, "Timed out while handling slice callback " + mCallback); + } + }; + /** * Implemented to create a slice. * <p> @@ -166,6 +428,75 @@ public abstract class SliceProvider extends ContentProviderWrapper { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Implementing this is optional for a SliceProvider, but does provide a good + * discovery mechanism for finding slice Uris. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see androidx.slice.SliceManager#getSliceDescendants(Uri) + */ + public Collection<Uri> onGetSliceDescendants(Uri uri) { + return Collections.emptyList(); + } + + @Nullable + @Override + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder) { + return null; + } + + @Nullable + @Override + @RequiresApi(28) + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { + return null; + } + + @Nullable + @Override + @RequiresApi(16) + public final Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable String selection, @Nullable String[] selectionArgs, + @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) { + return null; + } + + @Nullable + @Override + public final Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public final int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { + return 0; + } + + @Override + public final int delete(@NonNull Uri uri, @Nullable String selection, + @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public final int update(@NonNull Uri uri, @Nullable ContentValues values, + @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Nullable + @Override + @RequiresApi(19) + public final Uri canonicalize(@NonNull Uri url) { + return null; + } + + /** * @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) |