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

Commit 7ac3ea3f authored by Pinyao Ting's avatar Pinyao Ting Committed by Android (Google) Code Review
Browse files

Merge "Integrate with RemoteComposePlayer" into main

parents ec621683 51a18ed9
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) {