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

Commit fa4a42a9 authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Wire on click handlers for RemoteCanvas

Bug: 286130467
Test: manual
Change-Id: I01ac923775c01cbfacb48c1fe16aecebf5a46850
parent 1b5c7d20
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -22,10 +22,15 @@ import android.annotation.FlaggedApi;
import android.annotation.StyleRes;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import java.util.function.IntConsumer;

/**
 * {@link RemoteCanvas} is designed to support arbitrary protocols between two processes using
@@ -39,6 +44,25 @@ import androidx.annotation.Nullable;
@FlaggedApi(FLAG_DRAW_DATA_PARCEL)
public class RemoteCanvas extends View {

    private static final String TAG = "RemoteCanvas";

    @Nullable
    private SparseArray<Runnable> mCallbacks;

    private final IntConsumer mOnClickHandler = (viewId) -> {
        if (mCallbacks == null) {
            Log.w(TAG, "Cannot find callback for " + viewId
                    + ", in fact there were no callbacks from this RemoteViews at all.");
            return;
        }
        final Runnable cb = getCallbacks().get(viewId);
        if (cb != null) {
            cb.run();
        } else {
            Log.w(TAG, "Cannot find callback for " + viewId);
        }
    };

    RemoteCanvas(@NonNull Context context) {
        super(context);
    }
@@ -66,5 +90,28 @@ public class RemoteCanvas extends View {
    void setDrawInstructions(@NonNull final RemoteViews.DrawInstructions instructions) {
        setTag(instructions);
        // TODO: handle draw instructions
        // TODO: attach mOnClickHandler
    }

    /**
     * Adds a callback function to a clickable area in the RemoteCanvas.
     *
     * @param viewId the viewId of the clickable area
     * @param cb the callback function to be triggered when clicked
     */
    void addOnClickHandler(final int viewId, @NonNull final Runnable cb) {
        getCallbacks().set(viewId, cb);
    }

    /**
     * Returns all callbacks added to the RemoteCanvas through
     * {@link #addOnClickHandler(int, Runnable)}.
     */
    @VisibleForTesting
    public SparseArray<Runnable> getCallbacks() {
        if (mCallbacks == null) {
            mCallbacks = new SparseArray<>();
        }
        return mCallbacks;
    }
}
+61 −8
Original line number Diff line number Diff line
@@ -452,6 +452,12 @@ public class RemoteViews implements Parcelable, Filter {
     */
    private boolean mHasDrawInstructions;

    @Nullable
    private SparseArray<PendingIntent> mPendingIntentTemplate;

    @Nullable
    private SparseArray<Intent> mFillInIntent;

    private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
            (view, pendingIntent, response) ->
                    startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
@@ -1473,6 +1479,11 @@ public class RemoteViews implements Parcelable, Filter {

        @Override
        public void apply(View root, ViewGroup rootParent, ActionApplyParams params) {
            if (hasDrawInstructions() && root instanceof RemoteCanvas target) {
                target.addOnClickHandler(mViewId, () ->
                        mResponse.handleViewInteraction(root, params.handler));
                return;
            }
            final View target = root.findViewById(mViewId);
            if (target == null) return;

@@ -4814,8 +4825,13 @@ public class RemoteViews implements Parcelable, Filter {
     *          by a child of viewId and executed when that child is clicked
     */
    public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
        if (hasDrawInstructions()) {
            getPendingIntentTemplate().set(viewId, pendingIntentTemplate);
            tryAddRemoteResponse(viewId);
        } else {
            addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
        }
    }

    /**
     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
@@ -4835,8 +4851,13 @@ public class RemoteViews implements Parcelable, Filter {
     *        in order to determine the on-click behavior of the view specified by viewId
     */
    public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
        if (hasDrawInstructions()) {
            getFillInIntent().set(viewId, fillInIntent);
            tryAddRemoteResponse(viewId);
        } else {
            setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
        }
    }

    /**
     * Equivalent to calling
@@ -6463,6 +6484,32 @@ public class RemoteViews implements Parcelable, Filter {
        return context;
    }

    @NonNull
    private SparseArray<PendingIntent> getPendingIntentTemplate() {
        if (mPendingIntentTemplate == null) {
            mPendingIntentTemplate = new SparseArray<>();
        }
        return mPendingIntentTemplate;
    }

    @NonNull
    private SparseArray<Intent> getFillInIntent() {
        if (mFillInIntent == null) {
            mFillInIntent = new SparseArray<>();
        }
        return mFillInIntent;
    }

    private void tryAddRemoteResponse(final int viewId) {
        final PendingIntent pendingIntent = getPendingIntentTemplate().get(viewId);
        final Intent intent = getFillInIntent().get(viewId);
        if (pendingIntent != null && intent != null) {
            addAction(new SetOnClickResponse(viewId,
                    RemoteResponse.fromPendingIntentTemplateAndFillInIntent(
                            pendingIntent, intent)));
        }
    }

    /**
     * Utility class to hold all the options when applying the remote views
     * @hide
@@ -7002,6 +7049,14 @@ public class RemoteViews implements Parcelable, Filter {
            return response;
        }

        private static RemoteResponse fromPendingIntentTemplateAndFillInIntent(
                @NonNull final PendingIntent pendingIntent, @NonNull final Intent intent) {
            RemoteResponse response = new RemoteResponse();
            response.mPendingIntent = pendingIntent;
            response.mFillIntent = intent;
            return response;
        }

        /**
         * Adds a shared element to be transferred as part of the transition between Activities
         * using cross-Activity scene animations. The position of the first element will be used as
@@ -7040,8 +7095,8 @@ public class RemoteViews implements Parcelable, Filter {

        private void writeToParcel(Parcel dest, int flags) {
            PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
            if (mPendingIntent == null) {
                // Only write the intent if pending intent is null
            dest.writeBoolean((mFillIntent != null));
            if (mFillIntent != null) {
                dest.writeTypedObject(mFillIntent, flags);
            }
            dest.writeInt(mInteractionType);
@@ -7051,9 +7106,7 @@ public class RemoteViews implements Parcelable, Filter {

        private void readFromParcel(Parcel parcel) {
            mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
            if (mPendingIntent == null) {
                mFillIntent = parcel.readTypedObject(Intent.CREATOR);
            }
            mFillIntent = parcel.readBoolean() ? parcel.readTypedObject(Intent.CREATOR) : null;
            mInteractionType = parcel.readInt();
            int[] viewIds = parcel.createIntArray();
            mViewIds = viewIds == null ? null : IntArray.wrap(viewIds);
@@ -7130,7 +7183,7 @@ public class RemoteViews implements Parcelable, Filter {

        /** @hide */
        public Pair<Intent, ActivityOptions> getLaunchOptions(View view) {
            Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent);
            Intent intent = mFillIntent == null ? new Intent() : new Intent(mFillIntent);
            intent.setSourceBounds(getSourceBounds(view));

            if (view instanceof CompoundButton
+29 −5
Original line number Diff line number Diff line
@@ -422,17 +422,41 @@ public class RemoteViewsTest {
        if (!drawDataParcel()) {
            return;
        }
        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
        final RemoteViews rv = new RemoteViews(drawInstructions);
        final View view = rv.apply(mContext, mContainer);
        assertTrue(view instanceof RemoteCanvas);
        assertEquals(drawInstructions, view.getTag());
    }

    @Test
    public void remoteCanvasWiresClickHandlers() {
        if (!drawDataParcel()) {
            return;
        }
        final RemoteViews.DrawInstructions drawInstructions = getDrawInstructions();
        final RemoteViews rv = new RemoteViews(drawInstructions);
        final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
                new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
        final Intent i = new Intent().putExtra("TEST", "Success");
        final int viewId = 1;
        rv.setPendingIntentTemplate(viewId, pi);
        rv.setOnClickFillInIntent(viewId, i);
        final View view = rv.apply(mContext, mContainer);
        assertTrue(view instanceof RemoteCanvas);
        RemoteCanvas target = (RemoteCanvas) view;
        assertEquals(1, target.getCallbacks().size());
        assertNotNull(target.getCallbacks().get(viewId));
    }

    private RemoteViews.DrawInstructions getDrawInstructions() {
        final byte[] first = new byte[] {'f', 'i', 'r', 's', 't'};
        final byte[] second = new byte[] {'s', 'e', 'c', 'o', 'n', 'd'};
        final RemoteViews.DrawInstructions drawInstructions =
                new RemoteViews.DrawInstructions.Builder(
                        Collections.singletonList(first)).build();
        drawInstructions.appendInstructions(second);

        RemoteViews rv = new RemoteViews(drawInstructions);
        View view = rv.apply(mContext, mContainer);
        assertTrue(view instanceof RemoteCanvas);
        assertEquals(drawInstructions, view.getTag());
        return drawInstructions;
    }

    private RemoteViews createViewChained(int depth, String... texts) {