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

Commit 1b5c7d20 authored by Pinyao Ting's avatar Pinyao Ting
Browse files

Add API which uses RemoteViews as a vehicle for draw instructions

Bug: 286130467
Test: atest RemoteViewsTest
Change-Id: If944c679e50a42ca707389ac4423336135484a49
parent 1ef5cace
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -59247,6 +59247,7 @@ package android.widget {
    ctor public RemoteViews(@NonNull java.util.Map<android.util.SizeF,android.widget.RemoteViews>);
    ctor public RemoteViews(android.widget.RemoteViews);
    ctor public RemoteViews(android.os.Parcel);
    ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews(@NonNull android.widget.RemoteViews.DrawInstructions);
    method public void addStableView(@IdRes int, @NonNull android.widget.RemoteViews, int);
    method public void addView(@IdRes int, android.widget.RemoteViews);
    method public android.view.View apply(android.content.Context, android.view.ViewGroup);
@@ -59355,6 +59356,15 @@ package android.widget {
    ctor public RemoteViews.ActionException(String);
  }
  @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions {
    method @FlaggedApi("android.appwidget.flags.draw_data_parcel") public void appendInstructions(@NonNull byte[]);
  }
  @FlaggedApi("android.appwidget.flags.draw_data_parcel") public static final class RemoteViews.DrawInstructions.Builder {
    ctor @FlaggedApi("android.appwidget.flags.draw_data_parcel") public RemoteViews.DrawInstructions.Builder(@NonNull java.util.List<byte[]>);
    method @FlaggedApi("android.appwidget.flags.draw_data_parcel") @NonNull public android.widget.RemoteViews.DrawInstructions build();
  }
  public static final class RemoteViews.RemoteCollectionItems implements android.os.Parcelable {
    method public int describeContents();
    method public int getItemCount();
+70 −0
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.view.View;

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

/**
 * {@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 {

    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
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -18,3 +18,4 @@ package android.widget;

parcelable RemoteViews;
parcelable RemoteViews.RemoteCollectionItems;
parcelable RemoteViews.DrawInstructions;
+170 −2
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.widget;

import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL;
import static android.appwidget.flags.Flags.drawDataParcel;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;

@@ -243,6 +245,7 @@ public class RemoteViews implements Parcelable, Filter {
    private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
    private static final int SET_REMOTE_ADAPTER_TAG = 33;
    private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34;
    private static final int SET_DRAW_INSTRUCTION_TAG = 35;

    /** @hide **/
    @IntDef(prefix = "MARGIN_", value = {
@@ -442,6 +445,13 @@ public class RemoteViews implements Parcelable, Filter {
    @Nullable
    private LayoutInflater.Factory2 mLayoutInflaterFactory2;

    /**
     * Indicates whether this {@link RemoteViews} was instantiated with a {@link DrawInstructions}
     * object. {@link DrawInstructions} serves as an alternative protocol for the host process
     * to render.
     */
    private boolean mHasDrawInstructions;

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

    private static class SetDrawInstructionAction extends Action {

        @Nullable
        private final DrawInstructions mInstructions;

        SetDrawInstructionAction(@NonNull final DrawInstructions instructions) {
            mInstructions = instructions;
        }

        SetDrawInstructionAction(@NonNull final Parcel in) {
            if (drawDataParcel()) {
                mInstructions = DrawInstructions.readFromParcel(in);
            } else {
                mInstructions = null;
            }
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            if (drawDataParcel()) {
                DrawInstructions.writeToParcel(mInstructions, dest, flags);
            }
        }

        @Override
        public void apply(View root, ViewGroup rootParent, ActionApplyParams params)
                throws ActionException {
            if (drawDataParcel() && mInstructions != null
                    && root instanceof RemoteCanvas remoteCanvas) {
                remoteCanvas.setDrawInstructions(mInstructions);
            }
        }

        @Override
        public int getActionTag() {
            return SET_DRAW_INSTRUCTION_TAG;
        }
    }

    /**
     * Create a new RemoteViews object that will display the views contained
     * in the specified layout file.
@@ -4080,6 +4129,7 @@ public class RemoteViews implements Parcelable, Filter {
        mClassCookies = src.mClassCookies;
        mIdealSize = src.mIdealSize;
        mProviderInstanceId = src.mProviderInstanceId;
        mHasDrawInstructions = src.mHasDrawInstructions;

        if (src.hasLandscapeAndPortraitLayouts()) {
            mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot);
@@ -4114,12 +4164,26 @@ public class RemoteViews implements Parcelable, Filter {
    /**
     * Reads a RemoteViews object from a parcel.
     *
     * @param parcel
     * @param parcel the parcel object
     */
    public RemoteViews(Parcel parcel) {
        this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0);
    }

    /**
     * Instantiates a RemoteViews object using {@link DrawInstructions}, which serves as an
     * alternative to XML layout. {@link DrawInstructions} objects contains the instructions which
     * can be interpreted and rendered accordingly in the host process.
     *
     * @param drawInstructions The {@link DrawInstructions} object
     */
    @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
    public RemoteViews(@NonNull final DrawInstructions drawInstructions) {
        Objects.requireNonNull(drawInstructions);
        mHasDrawInstructions = true;
        addAction(new SetDrawInstructionAction(drawInstructions));
    }

    private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData,
            @Nullable ApplicationInfo info, int depth) {
        if (depth > MAX_NESTED_VIEWS
@@ -4178,6 +4242,7 @@ public class RemoteViews implements Parcelable, Filter {
        }
        mApplyFlags = parcel.readInt();
        mProviderInstanceId = parcel.readLong();
        mHasDrawInstructions = parcel.readBoolean();

        // Ensure that all descendants have their caches set up recursively.
        if (mIsRoot) {
@@ -4254,6 +4319,8 @@ public class RemoteViews implements Parcelable, Filter {
                return new AttributeReflectionAction(parcel);
            case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG:
                return new SetOnStylusHandwritingResponse(parcel);
            case SET_DRAW_INSTRUCTION_TAG:
                return new SetDrawInstructionAction(parcel);
            default:
                throw new ActionException("Tag " + tag + " not found");
        }
@@ -5791,6 +5858,10 @@ public class RemoteViews implements Parcelable, Filter {
        }
    }

    private boolean hasDrawInstructions() {
        return mHasDrawInstructions;
    }

    private RemoteViews getRemoteViewsToApply(Context context) {
        if (hasLandscapeAndPortraitLayouts()) {
            int orientation = context.getResources().getConfiguration().orientation;
@@ -5973,6 +6044,10 @@ public class RemoteViews implements Parcelable, Filter {
        if (applyThemeResId != 0) {
            inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
        }
        // If the RemoteViews contains draw instructions, just use it instead.
        if (rv.hasDrawInstructions()) {
            return new RemoteCanvas(inflationContext);
        }
        LayoutInflater inflater = LayoutInflater.from(context);

        // Clone inflater so we load resources from correct context and
@@ -6236,7 +6311,7 @@ public class RemoteViews implements Parcelable, Filter {

    /** @hide */
    public boolean canRecycleView(@Nullable View v) {
        if (v == null) {
        if (v == null || hasDrawInstructions()) {
            return false;
        }
        Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
@@ -6624,6 +6699,7 @@ public class RemoteViews implements Parcelable, Filter {
        }
        dest.writeInt(mApplyFlags);
        dest.writeLong(mProviderInstanceId);
        dest.writeBoolean(mHasDrawInstructions);

        dest.restoreAllowSquashing(prevSquashingAllowed);
    }
@@ -7412,6 +7488,98 @@ public class RemoteViews implements Parcelable, Filter {
        }
    }

    /**
     * A data parcel that carries the instructions to draw the RemoteViews, as an alternative to
     * XML layout.
     */
    @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
    public static final class DrawInstructions {

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

        private DrawInstructions() {
            throw new UnsupportedOperationException(
                    "DrawInstructions cannot be instantiate without instructions");
        }

        private DrawInstructions(@NonNull List<byte[]> instructions) {
            // Create and retain an immutable copy of given instructions.
            mInstructions = new ArrayList<>(instructions.size());
            for (byte[] instruction : instructions) {
                final int len = instruction.length;
                final byte[] target = new byte[len];
                System.arraycopy(instruction, 0, target, 0, len);
                mInstructions.add(target);
            }
        }

        @Nullable
        private static DrawInstructions readFromParcel(@NonNull final Parcel in) {
            int size = in.readInt();
            if (size == -1) {
                return null;
            }
            byte[] instruction;
            final List<byte[]> instructions = new ArrayList<>(size);
            for (int i = 0; i < size; i++) {
                instruction = new byte[in.readInt()];
                in.readByteArray(instruction);
                instructions.add(instruction);
            }
            return new DrawInstructions(instructions);
        }
        private static void writeToParcel(@Nullable final DrawInstructions drawInstructions,
                @NonNull final Parcel dest, final int flags) {
            if (drawInstructions == null) {
                dest.writeInt(-1);
                return;
            }
            final List<byte[]> instructions = drawInstructions.mInstructions;
            dest.writeInt(instructions.size());
            for (byte[] instruction : instructions) {
                dest.writeInt(instruction.length);
                dest.writeByteArray(instruction);
            }
        }

        /**
         * Append additional instructions to this {@link DrawInstructions} object.
         */
        @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
        public void appendInstructions(@NonNull final byte[] instructions) {
            mInstructions.add(instructions);
        }

        /**
         * Builder class for {@link DrawInstructions} objects.
         */
        @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
        public static final class Builder {

            private final List<byte[]> mInstructions;

            /**
             * Constructor.
             *
             * @param instructions Information to draw the RemoteViews.
             */
            @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
            public Builder(@NonNull final List<byte[]> instructions) {
                mInstructions = new ArrayList<>(instructions);
            }

            /**
             * Creates a {@link DrawInstructions} instance.
             */
            @NonNull
            @FlaggedApi(FLAG_DRAW_DATA_PARCEL)
            public DrawInstructions build() {
                return new DrawInstructions(mInstructions);
            }
        }
    }

    /**
     * Get the ID of the top-level view of the XML layout, if set using
     * {@link RemoteViews#RemoteViews(String, int, int)}.
+21 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.widget;

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

import static com.android.internal.R.id.pending_intent_tag;

import static org.junit.Assert.assertArrayEquals;
@@ -63,6 +65,7 @@ import org.junit.runner.RunWith;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -414,6 +417,24 @@ public class RemoteViewsTest {
        assertNotNull(view.findViewById(R.id.light_background_text));
    }

    @Test
    public void remoteCanvasCanAccessDrawInstructions() {
        if (!drawDataParcel()) {
            return;
        }
        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());
    }

    private RemoteViews createViewChained(int depth, String... texts) {
        RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);