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

Commit 51a18ed9 authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Integrate with RemoteComposePlayer

Bug: 286130467
Test: atest RemoteViewsTest
Change-Id: I68cf574f1383a5fdc57643b9c199effc85226754
parent 2b9a9e05
Loading
Loading
Loading
Loading
+0 −117
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package android.widget;

import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;

import android.annotation.AttrRes;
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
 * {@link RemoteViews.DrawInstructions}. Upon instantiation in the host process,
 * {@link RemoteCanvas#setDrawInstructions(RemoteViews.DrawInstructions)} is called so that the
 * host process can render the {@link RemoteViews.DrawInstructions} from the provider process
 * accordingly.
 *
 * @hide
 */
@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);
    }

    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
                 @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    RemoteCanvas(@NonNull Context context, @Nullable AttributeSet attrs,
                 @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    /**
     * Setter method for the {@link RemoteViews.DrawInstructions} from the provider process for
     * the host process to render accordingly.
     *
     * @param instructions {@link RemoteViews.DrawInstructions} from the provider process.
     */
    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;
    }
}
+27 −7
Original line number Diff line number Diff line
@@ -112,7 +112,10 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.IRemoteViewsFactory;
import com.android.internal.widget.remotecompose.player.RemoteComposeDocument;
import com.android.internal.widget.remotecompose.player.RemoteComposePlayer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -1479,9 +1482,7 @@ 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));
            if (hasDrawInstructions() && root instanceof RemoteComposePlayer) {
                return;
            }
            final View target = root.findViewById(mViewId);
@@ -3900,8 +3901,17 @@ public class RemoteViews implements Parcelable, Filter {
        public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
                throws ActionException {
            if (drawDataParcel() && mInstructions != null
                    && root instanceof RemoteCanvas remoteCanvas) {
                remoteCanvas.setDrawInstructions(mInstructions);
                    && root instanceof RemoteComposePlayer player) {
                player.setTag(mInstructions);
                final List<byte[]> bytes = mInstructions.mInstructions;
                if (bytes.isEmpty()) {
                    return;
                }
                try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) {
                    player.setDocument(new RemoteComposeDocument(is));
                } catch (IOException e) {
                    Log.e(LOG_TAG, "Failed to render draw instructions", e);
                }
            }
        }

@@ -6041,6 +6051,16 @@ public class RemoteViews implements Parcelable, Filter {
        RemoteViews rvToApply = getRemoteViewsToApply(context, size);
        View result = inflateView(context, rvToApply, directParent,
                params.applyThemeResId, params.colorResources);
        if (result instanceof RemoteComposePlayer player) {
            player.addClickListener((viewId, metadata) -> {
                mActions.forEach(action -> {
                    if (viewId == action.mViewId
                            && action instanceof SetOnClickResponse setOnClickResponse) {
                        setOnClickResponse.mResponse.handleViewInteraction(player, params.handler);
                    }
                });
            });
        }
        rvToApply.performApply(result, rootParent, params);
        return result;
    }
@@ -6064,7 +6084,7 @@ public class RemoteViews implements Parcelable, Filter {
        }
        // If the RemoteViews contains draw instructions, just use it instead.
        if (rv.hasDrawInstructions()) {
            return new RemoteCanvas(inflationContext);
            return new RemoteComposePlayer(inflationContext);
        }
        LayoutInflater inflater = LayoutInflater.from(context);

@@ -7546,7 +7566,7 @@ public class RemoteViews implements Parcelable, Filter {
    public static final class DrawInstructions {

        @NonNull
        private final List<byte[]> mInstructions;
        final List<byte[]> mInstructions;

        private DrawInstructions() {
            throw new UnsupportedOperationException(
+4 −22
Original line number Diff line number Diff line
@@ -422,19 +422,9 @@ 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 byte[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
        final RemoteViews.DrawInstructions drawInstructions =
                new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
        final RemoteViews rv = new RemoteViews(drawInstructions);
        final PendingIntent pi = PendingIntent.getActivity(mContext, 0,
                new Intent(Intent.ACTION_VIEW), PendingIntent.FLAG_IMMUTABLE);
@@ -443,15 +433,7 @@ public class RemoteViewsTest {
        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[] bytes = new byte[] {'h', 'e', 'l', 'l', 'o'};
        return new RemoteViews.DrawInstructions.Builder(Collections.singletonList(bytes)).build();
        assertEquals(drawInstructions, view.getTag());
    }

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