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
This commit is contained in:
Willie Koomson
2022-01-11 01:07:52 +00:00
parent e5c3230597
commit 3a83f0e9a9
6 changed files with 194 additions and 5 deletions

View File

@@ -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.

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2008, 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.
*/
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
**
** Copyright 2008, 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.
*/
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/themed_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/RelativeLayoutAlignTop25Alpha"/>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 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
-->
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

View File

@@ -34,6 +34,16 @@
<style name="LayoutInDisplayCutoutModeAlways">
<item name="android:windowLayoutInDisplayCutoutMode">always</item>
</style>
<style name="RelativeLayoutAlignBottom50Alpha">
<item name="android:layout_alignParentTop">false</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:alpha">0.5</item>
</style>
<style name="RelativeLayoutAlignTop25Alpha">
<item name="android:layout_alignParentTop">true</item>
<item name="android:layout_alignParentBottom">false</item>
<item name="android:alpha">0.25</item>
</style>
<style name="WindowBackgroundColorLiteral">
<item name="android:windowBackground">#00FF00</item>
</style>

View File

@@ -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;