diff options
| -rw-r--r-- | api/current.txt | 16 | ||||
| -rw-r--r-- | core/java/android/app/slice/SliceManager.java | 174 | ||||
| -rw-r--r-- | core/java/android/app/slice/SliceProvider.java | 24 |
3 files changed, 179 insertions, 35 deletions
diff --git a/api/current.txt b/api/current.txt index 23ba7f152f01..8a09b82bf770 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7092,6 +7092,20 @@ package android.app.slice { field public static final java.lang.String FORMAT_TIMESTAMP = "timestamp"; } + public class SliceManager { + method public java.util.List<android.app.slice.SliceSpec> getPinnedSpecs(android.net.Uri); + method public void pinSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); + method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List<android.app.slice.SliceSpec>); + method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List<android.app.slice.SliceSpec>, android.os.Handler); + method public void registerSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback, java.util.List<android.app.slice.SliceSpec>, java.util.concurrent.Executor); + method public void unpinSlice(android.net.Uri); + method public void unregisterSliceCallback(android.net.Uri, android.app.slice.SliceManager.SliceCallback); + } + + public static abstract interface SliceManager.SliceCallback { + method public abstract void onSliceUpdated(android.app.slice.Slice); + } + public abstract class SliceProvider extends android.content.ContentProvider { ctor public SliceProvider(); method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); @@ -7100,6 +7114,8 @@ package android.app.slice { method public android.app.slice.Slice onBindSlice(android.net.Uri, java.util.List<android.app.slice.SliceSpec>); method public deprecated android.app.slice.Slice onBindSlice(android.net.Uri); method public android.net.Uri onMapIntentToUri(android.content.Intent); + method public void onSlicePinned(android.net.Uri); + method public void onSliceUnpinned(android.net.Uri); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); diff --git a/core/java/android/app/slice/SliceManager.java b/core/java/android/app/slice/SliceManager.java index f8e19c12b810..0c5f225d515e 100644 --- a/core/java/android/app/slice/SliceManager.java +++ b/core/java/android/app/slice/SliceManager.java @@ -16,24 +16,37 @@ package android.app.slice; +import android.annotation.NonNull; import android.annotation.SystemService; -import android.app.slice.ISliceListener.Stub; import android.content.Context; import android.net.Uri; import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.util.ArrayMap; +import android.util.Pair; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Executor; /** - * @hide + * Class to handle interactions with {@link Slice}s. + * <p> + * The SliceManager manages permissions and pinned state for slices. */ @SystemService(Context.SLICE_SERVICE) public class SliceManager { private final ISliceManager mService; private final Context mContext; + private final ArrayMap<Pair<Uri, SliceCallback>, ISliceListener> mListenerLookup = + new ArrayMap<>(); + /** + * @hide + */ public SliceManager(Context context, Handler handler) throws ServiceNotFoundException { mContext = context; mService = ISliceManager.Stub.asInterface( @@ -41,38 +54,142 @@ 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) + */ + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs) { + registerSliceCallback(uri, callback, specs, Handler.getMain()); + } + + /** + * 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) */ - public void addSliceListener(Uri uri, SliceListener listener, SliceSpec[] specs) { + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs, Handler handler) { try { - mService.addSliceListener(uri, mContext.getPackageName(), listener.mStub, specs); + 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(); } } /** + * 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) */ - public void removeSliceListener(Uri uri, SliceListener listener) { + public void registerSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback, + @NonNull List<SliceSpec> specs, Executor executor) { try { - mService.removeSliceListener(uri, mContext.getPackageName(), listener.mStub); + mService.addSliceListener(uri, mContext.getPackageName(), + getListener(uri, callback, new ISliceListener.Stub() { + @Override + public void onSliceUpdated(Slice s) throws RemoteException { + executor.execute(() -> callback.onSliceUpdated(s)); + } + }), specs.toArray(new SliceSpec[specs.size()])); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + private ISliceListener getListener(Uri uri, SliceCallback callback, + ISliceListener listener) { + Pair<Uri, SliceCallback> key = new Pair<>(uri, callback); + if (mListenerLookup.containsKey(key)) { + try { + mService.removeSliceListener(uri, mContext.getPackageName(), + mListenerLookup.get(key)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + mListenerLookup.put(key, listener); + return listener; + } + /** + * Removes a callback for a specific slice uri. + * <p> + * Removes the app from the pinned state (if there are no other apps/callbacks pinning it) + * in addition to removing the callback. + * + * @param uri The uri of the slice being listened to + * @param callback The listener that should no longer receive callbacks. + * @see #registerSliceCallback */ - public void pinSlice(Uri uri, SliceSpec[] specs) { + public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) { try { - mService.pinSlice(mContext.getPackageName(), uri, specs); + mService.removeSliceListener(uri, mContext.getPackageName(), + mListenerLookup.remove(new Pair<>(uri, callback))); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Ensures that a slice is in a pinned state. + * <p> + * Pinned state is not persisted across reboots, so apps are expected to re-pin any slices + * they still care about after a reboot. + * + * @param uri The uri of the slice being pinned. + * @param specs The list of supported {@link SliceSpec}s of the callback. + * @see SliceProvider#onSlicePinned(Uri) */ - public void unpinSlice(Uri uri) { + public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) { + try { + mService.pinSlice(mContext.getPackageName(), uri, + specs.toArray(new SliceSpec[specs.size()])); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Remove a pin for a slice. + * <p> + * If the slice has no other pins/callbacks then the slice will be unpinned. + * + * @param uri The uri of the slice being unpinned. + * @see #pinSlice + * @see SliceProvider#onSliceUnpinned(Uri) + */ + public void unpinSlice(@NonNull Uri uri) { try { mService.unpinSlice(mContext.getPackageName(), uri); } catch (RemoteException e) { @@ -81,6 +198,7 @@ public class SliceManager { } /** + * @hide */ public boolean hasSliceAccess() { try { @@ -91,41 +209,31 @@ public class SliceManager { } /** + * Get the current set of specs for a pinned slice. + * <p> + * This is the set of specs supported for a specific pinned slice. It will take + * into account all clients and returns only specs supported by all. + * @see SliceSpec */ - public SliceSpec[] getPinnedSpecs(Uri uri) { + public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) { try { - return mService.getPinnedSpecs(uri, mContext.getPackageName()); + return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName())); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** + * Class that listens to changes in {@link Slice}s. */ - public abstract static class SliceListener { - private final Handler mHandler; - - /** - */ - public SliceListener() { - this(Handler.getMain()); - } + public interface SliceCallback { /** + * Called when slice is updated. + * + * @param s The updated slice. + * @see #registerSliceCallback */ - public SliceListener(Handler h) { - mHandler = h; - } - - /** - */ - public abstract void onSliceUpdated(Slice s); - - private final ISliceListener.Stub mStub = new Stub() { - @Override - public void onSliceUpdated(Slice s) throws RemoteException { - mHandler.post(() -> SliceListener.this.onSliceUpdated(s)); - } - }; + void onSliceUpdated(Slice s); } } diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java index 7dcd2fead73f..8483931ceaec 100644 --- a/core/java/android/app/slice/SliceProvider.java +++ b/core/java/android/app/slice/SliceProvider.java @@ -151,13 +151,33 @@ public abstract class SliceProvider extends ContentProvider { } /** - * @hide + * Called to inform an app that a slice has been pinned. + * <p> + * Pinning is a way that slice hosts use to notify apps of which slices + * they care about updates for. When a slice is pinned the content is + * expected to be relatively fresh and kept up to date. + * <p> + * Being pinned does not provide any escalated privileges for the slice + * provider. So apps should do things such as turn on syncing or schedule + * a job in response to a onSlicePinned. + * <p> + * Pinned state is not persisted through a reboot, and apps can expect a + * new call to onSlicePinned for any slices that should remain pinned + * after a reboot occurs. + * + * @param sliceUri The uri of the slice being unpinned. + * @see #onSliceUnpinned(Uri) */ public void onSlicePinned(Uri sliceUri) { } /** - * @hide + * Called to inform an app that a slices is no longer pinned. + * <p> + * This means that no other apps on the device care about updates to this + * slice anymore and therefore it is not important to be updated. Any syncs + * or jobs related to this slice should be cancelled. + * @see #onSlicePinned(Uri) */ public void onSliceUnpinned(Uri sliceUri) { } |