summaryrefslogtreecommitdiff
path: root/android/app/slice/SliceManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'android/app/slice/SliceManager.java')
-rw-r--r--android/app/slice/SliceManager.java240
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 {