From 3a83f0e9a9f6be24076104faadc48b7be4ea4ed7 Mon Sep 17 00:00:00 2001 From: Willie Koomson Date: Tue, 11 Jan 2022 01:07:52 +0000 Subject: [PATCH 1/2] Make setRemoteAdapter() work for nested RemoteViews This change adds applyNestedView() and reapplyNestedView() to RemoteViews. This makes it possible to pass the top-level "rootParent" view (usually AppWidgetHostView for widgets) to nested RemoteViews that are added by addView(). This allows setRemoteAdapter actions to work properly when they are used in nested RemoteViews. Bug: 214288099 Test: RemoteViewsTest Change-Id: I4fa3fe98d89bffdc0a5be46dabed12b9c217219e --- core/java/android/widget/RemoteViews.java | 26 +++++- .../layout/remote_view_relative_layout.xml | 24 +++++ ...remote_view_relative_layout_with_theme.xml | 25 +++++ .../res/layout/remote_views_list.xml | 21 +++++ core/tests/coretests/res/values/styles.xml | 10 ++ .../src/android/widget/RemoteViewsTest.java | 93 +++++++++++++++++++ 6 files changed, 194 insertions(+), 5 deletions(-) create mode 100644 core/tests/coretests/res/layout/remote_view_relative_layout.xml create mode 100644 core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml create mode 100644 core/tests/coretests/res/layout/remote_views_list.xml diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index c6f64f4ad6337..191962df9292b 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -2419,8 +2419,8 @@ public class RemoteViews implements Parcelable, Filter { target.removeViews(nextChild, recycledViewIndex - nextChild); } setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); - rvToApply.reapply(context, child, handler, null /* size */, colorResources, - false /* topLevel */); + rvToApply.reapplyNestedViews(context, child, rootParent, handler, + null /* size */, colorResources); return; } // If we cannot recycle the views, we still remove all views in between to @@ -2431,8 +2431,8 @@ public class RemoteViews implements Parcelable, Filter { // If we cannot recycle, insert the new view before the next recyclable child. // Inflate nested views and add as children - View nestedView = rvToApply.apply(context, target, handler, null /* size */, - colorResources); + View nestedView = rvToApply.applyNestedViews(context, target, rootParent, handler, + null /* size */, colorResources); if (mStableId != NO_ID) { setStableId(nestedView, mStableId); } @@ -3780,7 +3780,7 @@ public class RemoteViews implements Parcelable, Filter { * @param parcel */ public RemoteViews(Parcel parcel) { - this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0); + this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0); } private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData, @@ -5580,6 +5580,16 @@ public class RemoteViews implements Parcelable, Filter { return result; } + private View applyNestedViews(Context context, ViewGroup directParent, + ViewGroup rootParent, InteractionHandler handler, SizeF size, + ColorResources colorResources) { + RemoteViews rvToApply = getRemoteViewsToApply(context, size); + + View result = inflateView(context, rvToApply, directParent, 0, colorResources); + rvToApply.performApply(result, rootParent, handler, colorResources); + return result; + } + private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { return inflateView(context, rv, parent, 0, null); } @@ -5895,6 +5905,12 @@ public class RemoteViews implements Parcelable, Filter { } } + private void reapplyNestedViews(Context context, View v, ViewGroup rootParent, + InteractionHandler handler, SizeF size, ColorResources colorResources) { + RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); + rvToApply.performApply(v, rootParent, handler, colorResources); + } + /** * Applies all the actions to the provided view, moving as much of the task on the background * thread as possible. diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout.xml b/core/tests/coretests/res/layout/remote_view_relative_layout.xml new file mode 100644 index 0000000000000..713a4c89ea4d1 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_view_relative_layout.xml @@ -0,0 +1,24 @@ + + + + diff --git a/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml new file mode 100644 index 0000000000000..74c939b7eaa32 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_view_relative_layout_with_theme.xml @@ -0,0 +1,25 @@ + + + + diff --git a/core/tests/coretests/res/layout/remote_views_list.xml b/core/tests/coretests/res/layout/remote_views_list.xml new file mode 100644 index 0000000000000..ca43bc8986e74 --- /dev/null +++ b/core/tests/coretests/res/layout/remote_views_list.xml @@ -0,0 +1,21 @@ + + + + diff --git a/core/tests/coretests/res/values/styles.xml b/core/tests/coretests/res/values/styles.xml index 352b4dceb3cc8..32eebb35e0c3a 100644 --- a/core/tests/coretests/res/values/styles.xml +++ b/core/tests/coretests/res/values/styles.xml @@ -34,6 +34,16 @@ + + diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 059c764213bce..f026d97535be6 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -18,6 +18,7 @@ package android.widget; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -31,7 +32,9 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Binder; +import android.os.Looper; import android.os.Parcel; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; @@ -261,6 +264,55 @@ public class RemoteViewsTest { verifyViewTree(syncView, asyncView, "row1-c1", "row1-c2", "row1-c3", "row2-c1", "row2-c2"); } + @Test + public void nestedViews_setRemoteAdapter_intent() { + Looper.prepare(); + + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list); + inner2.setRemoteAdapter(R.id.list, new Intent()); + inner1.addView(R.id.container, inner2); + top.addView(R.id.container, inner1); + + View view = top.apply(mContext, widget); + widget.addView(view); + + ListView listView = (ListView) view.findViewById(R.id.list); + listView.onRemoteAdapterConnected(); + assertNotNull(listView.getAdapter()); + + top.reapply(mContext, view); + listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + } + + @Test + public void nestedViews_setRemoteAdapter_remoteCollectionItems() { + AppWidgetHostView widget = new AppWidgetHostView(mContext); + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner1 = new RemoteViews(mPackage, R.layout.remote_view_host); + RemoteViews inner2 = new RemoteViews(mPackage, R.layout.remote_views_list); + inner2.setRemoteAdapter( + R.id.list, + new RemoteViews.RemoteCollectionItems.Builder() + .addItem(0, new RemoteViews(mPackage, R.layout.remote_view_host)) + .build()); + inner1.addView(R.id.container, inner2); + top.addView(R.id.container, inner1); + + View view = top.apply(mContext, widget); + widget.addView(view); + + ListView listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + + top.reapply(mContext, view); + listView = (ListView) view.findViewById(R.id.list); + assertNotNull(listView.getAdapter()); + } + private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host); @@ -483,6 +535,47 @@ public class RemoteViewsTest { index, inflated.getTag(com.android.internal.R.id.notification_action_index_tag)); } + @Test + public void nestedViews_themesPropagateCorrectly() { + Context themedContext = + new ContextThemeWrapper(mContext, R.style.RelativeLayoutAlignBottom50Alpha); + RelativeLayout rootParent = new RelativeLayout(themedContext); + + RemoteViews top = new RemoteViews(mPackage, R.layout.remote_view_relative_layout); + RemoteViews inner1 = + new RemoteViews(mPackage, R.layout.remote_view_relative_layout_with_theme); + RemoteViews inner2 = + new RemoteViews(mPackage, R.layout.remote_view_relative_layout); + + inner1.addView(R.id.themed_layout, inner2); + top.addView(R.id.container, inner1); + + RelativeLayout root = (RelativeLayout) top.apply(themedContext, rootParent); + assertEquals(0.5, root.getAlpha(), 0.); + RelativeLayout.LayoutParams rootParams = + (RelativeLayout.LayoutParams) root.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + rootParams.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)); + + // The theme is set on inner1View and its descendants. However, inner1View does + // not get its layout params from its theme (though its descendants do), but other + // attributes such as alpha are set. + RelativeLayout inner1View = (RelativeLayout) root.getChildAt(0); + assertEquals(R.id.themed_layout, inner1View.getId()); + assertEquals(0.25, inner1View.getAlpha(), 0.); + RelativeLayout.LayoutParams inner1Params = + (RelativeLayout.LayoutParams) inner1View.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + inner1Params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)); + + RelativeLayout inner2View = (RelativeLayout) inner1View.getChildAt(0); + assertEquals(0.25, inner2View.getAlpha(), 0.); + RelativeLayout.LayoutParams inner2Params = + (RelativeLayout.LayoutParams) inner2View.getLayoutParams(); + assertEquals(RelativeLayout.TRUE, + inner2Params.getRule(RelativeLayout.ALIGN_PARENT_TOP)); + } + private class WidgetContainer extends AppWidgetHostView { int[] mSharedViewIds; String[] mSharedViewNames; From 35641ae2c08a764d9480d65e4081b22473379bc4 Mon Sep 17 00:00:00 2001 From: Willie Koomson Date: Wed, 12 Jan 2022 22:08:26 +0000 Subject: [PATCH 2/2] Propagate apply flags to nested RemoteViews This change propagates FLAG_WIDGET_IS_COLLECTION_CHILD and FLAG_USE_LIGHT_BACKGROUND_LAYOUT to nested RemoteViews (either added by addView, or set as portrait/landscape/sized RemoteViews) if that flag is also present on the parent RemoteView. For FLAG_WIDGET_IS_COLLECTION_CHILD, this prevents a PendingIntent from being set on a RemoteView that is a child of a collection item RemoteView. For FLAG_USE_LIGHT_BACKGROUND_LAYOUT, this ensures that nested RemoteViews use their light background layout if this is set on the parent. Bug: 214288099 Test: RemoteViewsTest Change-Id: I8da92d0d338631a99ee718a136bf9674827437bb --- core/java/android/widget/RemoteViews.java | 23 +++++ .../remote_views_light_background_text.xml | 21 ++++ .../src/android/widget/RemoteViewsTest.java | 98 +++++++++++++++++++ 3 files changed, 142 insertions(+) create mode 100644 core/tests/coretests/res/layout/remote_views_light_background_text.xml diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 191962df9292b..b00a3829f4682 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -300,6 +300,13 @@ public class RemoteViews implements Parcelable, Filter { */ public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; + /** + * This mask determines which flags are propagated to nested RemoteViews (either added by + * addView, or set as portrait/landscape/sized RemoteViews). + */ + static final int FLAG_MASK_TO_PROPAGATE = + FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT; + /** * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is * intentionally a different instance in order to trick Bundle reader so that it doesn't allow @@ -467,6 +474,18 @@ public class RemoteViews implements Parcelable, Filter { */ public void addFlags(@ApplyFlags int flags) { mApplyFlags = mApplyFlags | flags; + + int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE; + if (flagsToPropagate != 0) { + if (hasSizedRemoteViews()) { + for (RemoteViews remoteView : mSizedRemoteViews) { + remoteView.addFlags(flagsToPropagate); + } + } else if (hasLandscapeAndPortraitLayouts()) { + mLandscape.addFlags(flagsToPropagate); + mPortrait.addFlags(flagsToPropagate); + } + } } /** @@ -2407,6 +2426,10 @@ public class RemoteViews implements Parcelable, Filter { // will return -1. final int nextChild = getNextRecyclableChild(target); RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); + + int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE; + if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate); + if (nextChild >= 0 && mStableId != NO_ID) { // At that point, the views starting at index nextChild are the ones recyclable but // not yet recycled. All views added on that round of application are placed before. diff --git a/core/tests/coretests/res/layout/remote_views_light_background_text.xml b/core/tests/coretests/res/layout/remote_views_light_background_text.xml new file mode 100644 index 0000000000000..f300f0991a97a --- /dev/null +++ b/core/tests/coretests/res/layout/remote_views_light_background_text.xml @@ -0,0 +1,21 @@ + + + + diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index f026d97535be6..00b3693c902bb 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -16,9 +16,12 @@ package android.widget; +import static com.android.internal.R.id.pending_intent_tag; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -34,6 +37,7 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; import android.os.Parcel; +import android.util.SizeF; import android.view.ContextThemeWrapper; import android.view.View; import android.view.ViewGroup; @@ -52,6 +56,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; +import java.util.Map; import java.util.concurrent.CountDownLatch; /** @@ -313,6 +318,99 @@ public class RemoteViewsTest { assertNotNull(listView.getAdapter()); } + @Test + public void nestedViews_collectionChildFlag() throws Exception { + RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text); + nested.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews(mPackage, R.layout.remote_view_host); + listItem.addView(R.id.container, nested); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void landscapePortraitViews_collectionChildFlag() throws Exception { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews(inner, inner); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void sizedViews_collectionChildFlag() throws Exception { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setOnClickPendingIntent( + R.id.text, + PendingIntent.getActivity(mContext, 0, new Intent(), PendingIntent.FLAG_MUTABLE) + ); + + RemoteViews listItem = new RemoteViews( + Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner)); + listItem.addFlags(RemoteViews.FLAG_WIDGET_IS_COLLECTION_CHILD); + + View view = listItem.apply(mContext, mContainer); + TextView text = (TextView) view.findViewById(R.id.text); + assertNull(text.getTag(pending_intent_tag)); + } + + @Test + public void nestedViews_lightBackgroundLayoutFlag() { + RemoteViews nested = new RemoteViews(mPackage, R.layout.remote_views_text); + nested.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews(mPackage, R.layout.remote_view_host); + parent.addView(R.id.container, nested); + parent.setLightBackgroundLayoutId(R.layout.remote_view_host); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + + @Test + public void landscapePortraitViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews(inner, inner); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + + @Test + public void sizedViews_lightBackgroundLayoutFlag() { + RemoteViews inner = new RemoteViews(mPackage, R.layout.remote_views_text); + inner.setLightBackgroundLayoutId(R.layout.remote_views_light_background_text); + + RemoteViews parent = new RemoteViews( + Map.of(new SizeF(0, 0), inner, new SizeF(100, 100), inner)); + parent.addFlags(RemoteViews.FLAG_USE_LIGHT_BACKGROUND_LAYOUT); + + View view = parent.apply(mContext, mContainer); + assertNull(view.findViewById(R.id.text)); + assertNotNull(view.findViewById(R.id.light_background_text)); + } + private RemoteViews createViewChained(int depth, String... texts) { RemoteViews result = new RemoteViews(mPackage, R.layout.remote_view_host);