diff options
Diffstat (limited to 'android/app/slice/SliceManager.java')
-rw-r--r-- | android/app/slice/SliceManager.java | 240 |
1 files changed, 213 insertions, 27 deletions
diff --git a/android/app/slice/SliceManager.java b/android/app/slice/SliceManager.java index 0c5f225d..2fa9d8e0 100644 --- a/android/app/slice/SliceManager.java +++ b/android/app/slice/SliceManager.java @@ -16,18 +16,31 @@ package android.app.slice; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemService; +import android.content.ContentResolver; import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.pm.ResolveInfo; import android.net.Uri; +import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; import android.util.ArrayMap; +import android.util.Log; import android.util.Pair; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; @@ -39,12 +52,36 @@ import java.util.concurrent.Executor; @SystemService(Context.SLICE_SERVICE) public class SliceManager { + private static final String TAG = "SliceManager"; + + /** + * @hide + */ + public static final String ACTION_REQUEST_SLICE_PERMISSION = + "android.intent.action.REQUEST_SLICE_PERMISSION"; + private final ISliceManager mService; private final Context mContext; private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = new ArrayMap<>(); /** + * Permission denied. + * @hide + */ + public static final int PERMISSION_DENIED = -1; + /** + * Permission granted. + * @hide + */ + public static final int PERMISSION_GRANTED = 0; + /** + * Permission just granted by the user, and should be granted uri permission as well. + * @hide + */ + public static final int PERMISSION_USER_GRANTED = 1; + + /** * @hide */ public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { @@ -54,21 +91,21 @@ public class SliceManager { } /** - * Adds a callback to a specific slice uri. - * <p> - * This is a convenience that performs a few slice actions at once. It will put - * the slice in a pinned state since there is a callback attached. It will also - * listen for content changes, when a content change observes, the android system - * will bind the new slice and provide it to all registered {@link SliceCallback}s. - * - * @param uri The uri of the slice being listened to. - * @param callback The listener that should receive the callbacks. - * @param specs The list of supported {@link SliceSpec}s of the callback. - * @see SliceProvider#onSlicePinned(Uri) + * @deprecated TO BE REMOVED. */ + @Deprecated public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, @NonNull List<SliceSpec> specs) { - registerSliceCallback(uri, callback, specs, Handler.getMain()); + registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); + } + + /** + * @deprecated TO BE REMOVED. + */ + @Deprecated + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs, Executor executor) { + registerSliceCallback(uri, specs, executor, callback); } /** @@ -84,19 +121,9 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs, Handler handler) { - try { - mService.addSliceListener(uri, mContext.getPackageName(), - getListener(uri, callback, new ISliceListener.Stub() { - @Override - public void onSliceUpdated(Slice s) throws RemoteException { - handler.post(() -> callback.onSliceUpdated(s)); - } - }), specs.toArray(new SliceSpec[specs.size()])); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs, + @NonNull SliceCallback callback) { + registerSliceCallback(uri, specs, mContext.getMainExecutor(), callback); } /** @@ -112,8 +139,8 @@ public class SliceManager { * @param specs The list of supported {@link SliceSpec}s of the callback. * @see SliceProvider#onSlicePinned(Uri) */ - public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, - @NonNull List<SliceSpec> specs, Executor executor) { + public void registerSliceCallback(@NonNull Uri uri, @NonNull List<SliceSpec> specs, + @NonNull @CallbackExecutor Executor executor, @NonNull SliceCallback callback) { try { mService.addSliceListener(uri, mContext.getPackageName(), getListener(uri, callback, new ISliceListener.Stub() { @@ -224,6 +251,165 @@ public class SliceManager { } /** + * Obtains a list of slices that are descendants of the specified Uri. + * <p> + * Not all slice providers will implement this functionality, in which case, + * an empty collection will be returned. + * + * @param uri The uri to look for descendants under. + * @return All slices within the space. + * @see SliceProvider#onGetSliceDescendants(Uri) + */ + public @NonNull Collection<Uri> getSliceDescendants(@NonNull Uri uri) { + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + final Bundle res = provider.call(resolver.getPackageName(), + SliceProvider.METHOD_GET_DESCENDANTS, null, extras); + return res.getParcelableArrayList(SliceProvider.EXTRA_SLICE_DESCENDANTS); + } catch (RemoteException e) { + Log.e(TAG, "Unable to get slice descendants", e); + } finally { + resolver.releaseProvider(provider); + } + return Collections.emptyList(); + } + + /** + * Turns a slice Uri into slice content. + * + * @param uri The URI to a slice provider + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + */ + public @Nullable Slice bindSlice(@NonNull Uri uri, @NonNull List<SliceSpec> supportedSpecs) { + Preconditions.checkNotNull(uri, "uri"); + ContentResolver resolver = mContext.getContentResolver(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_BIND_URI, uri); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(mContext.getPackageName(), SliceProvider.METHOD_SLICE, + null, extras); + Bundle.setDefusable(res, true); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Turns a slice intent into slice content. Expects an explicit intent. If there is no + * {@link android.content.ContentProvider} associated with the given intent this will throw + * {@link IllegalArgumentException}. + * + * @param intent The intent associated with a slice. + * @param supportedSpecs List of supported specs. + * @return The Slice provided by the app or null if none is given. + * @see Slice + * @see SliceProvider#onMapIntentToUri(Intent) + * @see Intent + */ + public @Nullable Slice bindSlice(@NonNull Intent intent, + @NonNull List<SliceSpec> supportedSpecs) { + Preconditions.checkNotNull(intent, "intent"); + Preconditions.checkArgument(intent.getComponent() != null || intent.getPackage() != null, + "Slice intent must be explicit " + intent); + ContentResolver resolver = mContext.getContentResolver(); + + // Check if the intent has data for the slice uri on it and use that + final Uri intentData = intent.getData(); + if (intentData != null && SliceProvider.SLICE_TYPE.equals(resolver.getType(intentData))) { + return bindSlice(intentData, supportedSpecs); + } + // Otherwise ask the app + List<ResolveInfo> providers = + mContext.getPackageManager().queryIntentContentProviders(intent, 0); + if (providers == null) { + throw new IllegalArgumentException("Unable to resolve intent " + intent); + } + String authority = providers.get(0).providerInfo.authority; + Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).build(); + IContentProvider provider = resolver.acquireProvider(uri); + if (provider == null) { + throw new IllegalArgumentException("Unknown URI " + uri); + } + try { + Bundle extras = new Bundle(); + extras.putParcelable(SliceProvider.EXTRA_INTENT, intent); + extras.putParcelableArrayList(SliceProvider.EXTRA_SUPPORTED_SPECS, + new ArrayList<>(supportedSpecs)); + final Bundle res = provider.call(mContext.getPackageName(), + SliceProvider.METHOD_MAP_INTENT, null, extras); + if (res == null) { + return null; + } + return res.getParcelable(SliceProvider.EXTRA_SLICE); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return null; + } finally { + resolver.releaseProvider(provider); + } + } + + /** + * Does the permission check to see if a caller has access to a specific slice. + * @hide + */ + public void enforceSlicePermission(Uri uri, String pkg, int pid, int uid) { + try { + if (pkg == null) { + throw new SecurityException("No pkg specified"); + } + int result = mService.checkSlicePermission(uri, pkg, pid, uid); + if (result == PERMISSION_DENIED) { + throw new SecurityException("User " + uid + " does not have slice permission for " + + uri + "."); + } + if (result == PERMISSION_USER_GRANTED) { + // We just had a user grant of this permission and need to grant this to the app + // permanently. + mContext.grantUriPermission(pkg, uri.buildUpon().path("").build(), + Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION + | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called by SystemUI to grant a slice permission after a dialog is shown. + * @hide + */ + public void grantPermissionFromUser(Uri uri, String pkg, boolean allSlices) { + try { + mService.grantPermissionFromUser(uri, pkg, mContext.getPackageName(), allSlices); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Class that listens to changes in {@link Slice}s. */ public interface SliceCallback { |