Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d521a54 authored by Jason Monk's avatar Jason Monk Committed by Android (Google) Code Review
Browse files

Merge "Add APIs for slice pinning"

parents e67de60f e2c64517
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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);
+141 −33
Original line number Diff line number Diff line
@@ -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,91 +54,186 @@ 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();
        }
    }

    /**
     */
    public void pinSlice(Uri uri, SliceSpec[] specs) {
    private ISliceListener getListener(Uri uri, SliceCallback callback,
            ISliceListener listener) {
        Pair<Uri, SliceCallback> key = new Pair<>(uri, callback);
        if (mListenerLookup.containsKey(key)) {
            try {
            mService.pinSlice(mContext.getPackageName(), uri, specs);
                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 unpinSlice(Uri uri) {
    public void unregisterSliceCallback(@NonNull Uri uri, @NonNull SliceCallback callback) {
        try {
            mService.unpinSlice(mContext.getPackageName(), uri);
            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 boolean hasSliceAccess() {
    public void pinSlice(@NonNull Uri uri, @NonNull List<SliceSpec> specs) {
        try {
            return mService.hasSliceAccess(mContext.getPackageName());
            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 SliceSpec[] getPinnedSpecs(Uri uri) {
    public void unpinSlice(@NonNull Uri uri) {
        try {
            return mService.getPinnedSpecs(uri, mContext.getPackageName());
            mService.unpinSlice(mContext.getPackageName(), uri);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * @hide
     */
    public abstract static class SliceListener {
        private final Handler mHandler;
    public boolean hasSliceAccess() {
        try {
            return mService.hasSliceAccess(mContext.getPackageName());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * 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 SliceListener() {
            this(Handler.getMain());
    public @NonNull List<SliceSpec> getPinnedSpecs(Uri uri) {
        try {
            return Arrays.asList(mService.getPinnedSpecs(uri, mContext.getPackageName()));
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Class that listens to changes in {@link Slice}s.
     */
        public SliceListener(Handler h) {
            mHandler = h;
        }
    public interface SliceCallback {

        /**
         * Called when slice is updated.
         *
         * @param s The updated slice.
         * @see #registerSliceCallback
         */
        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);
    }
}
+22 −2
Original line number Diff line number Diff line
@@ -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) {
    }