Loading core/api/current.txt +10 −0 Original line number Diff line number Diff line Loading @@ -59297,6 +59297,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); Loading Loading @@ -59405,6 +59406,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(); core/java/android/widget/RemoteCanvas.java 0 → 100644 +117 −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.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; } } core/java/android/widget/RemoteViews.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,4 @@ package android.widget; parcelable RemoteViews; parcelable RemoteViews.RemoteCollectionItems; parcelable RemoteViews.DrawInstructions; core/java/android/widget/RemoteViews.java +231 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = { Loading Loading @@ -442,6 +445,19 @@ 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; @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)); Loading Loading @@ -1463,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; Loading Loading @@ -3851,6 +3872,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. Loading Loading @@ -4080,6 +4140,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); Loading Loading @@ -4114,12 +4175,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 Loading Loading @@ -4178,6 +4253,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) { Loading Loading @@ -4254,6 +4330,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"); } Loading Loading @@ -4747,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 Loading @@ -4768,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 Loading Loading @@ -5791,6 +5879,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; Loading Loading @@ -5973,6 +6065,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 Loading Loading @@ -6236,7 +6332,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); Loading Loading @@ -6388,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 Loading Loading @@ -6624,6 +6746,7 @@ public class RemoteViews implements Parcelable, Filter { } dest.writeInt(mApplyFlags); dest.writeLong(mProviderInstanceId); dest.writeBoolean(mHasDrawInstructions); dest.restoreAllowSquashing(prevSquashingAllowed); } Loading Loading @@ -6926,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 Loading Loading @@ -6964,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); Loading @@ -6975,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); Loading Loading @@ -7054,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 Loading Loading @@ -7412,6 +7541,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)}. Loading core/tests/coretests/src/android/widget/RemoteViewsTest.java +45 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -414,6 +417,48 @@ public class RemoteViewsTest { assertNotNull(view.findViewById(R.id.light_background_text)); } @Test public void remoteCanvasCanAccessDrawInstructions() { 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); return drawInstructions; } private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); Loading Loading
core/api/current.txt +10 −0 Original line number Diff line number Diff line Loading @@ -59297,6 +59297,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); Loading Loading @@ -59405,6 +59406,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();
core/java/android/widget/RemoteCanvas.java 0 → 100644 +117 −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.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; } }
core/java/android/widget/RemoteViews.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,4 @@ package android.widget; parcelable RemoteViews; parcelable RemoteViews.RemoteCollectionItems; parcelable RemoteViews.DrawInstructions;
core/java/android/widget/RemoteViews.java +231 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 = { Loading Loading @@ -442,6 +445,19 @@ 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; @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)); Loading Loading @@ -1463,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; Loading Loading @@ -3851,6 +3872,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. Loading Loading @@ -4080,6 +4140,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); Loading Loading @@ -4114,12 +4175,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 Loading Loading @@ -4178,6 +4253,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) { Loading Loading @@ -4254,6 +4330,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"); } Loading Loading @@ -4747,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 Loading @@ -4768,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 Loading Loading @@ -5791,6 +5879,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; Loading Loading @@ -5973,6 +6065,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 Loading Loading @@ -6236,7 +6332,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); Loading Loading @@ -6388,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 Loading Loading @@ -6624,6 +6746,7 @@ public class RemoteViews implements Parcelable, Filter { } dest.writeInt(mApplyFlags); dest.writeLong(mProviderInstanceId); dest.writeBoolean(mHasDrawInstructions); dest.restoreAllowSquashing(prevSquashingAllowed); } Loading Loading @@ -6926,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 Loading Loading @@ -6964,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); Loading @@ -6975,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); Loading Loading @@ -7054,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 Loading Loading @@ -7412,6 +7541,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)}. Loading
core/tests/coretests/src/android/widget/RemoteViewsTest.java +45 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -414,6 +417,48 @@ public class RemoteViewsTest { assertNotNull(view.findViewById(R.id.light_background_text)); } @Test public void remoteCanvasCanAccessDrawInstructions() { 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); return drawInstructions; } private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); Loading