Loading core/java/android/widget/RemoteCanvas.javadeleted 100644 → 0 +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; } } core/java/android/widget/RemoteViews.java +27 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } } } Loading Loading @@ -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; } Loading @@ -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); Loading Loading @@ -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( Loading core/tests/coretests/src/android/widget/RemoteViewsTest.java +4 −22 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading Loading
core/java/android/widget/RemoteCanvas.javadeleted 100644 → 0 +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; } }
core/java/android/widget/RemoteViews.java +27 −7 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } } } Loading Loading @@ -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; } Loading @@ -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); Loading Loading @@ -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( Loading
core/tests/coretests/src/android/widget/RemoteViewsTest.java +4 −22 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) { Loading