Correct recycling to take setViewId into account.

Views shouldn't be recycled if the view id is changed as the identity
of the view is then altered.

Second pass: this has been further tested by adding/removing AppWidgets
on a test phone.

Bug: 181985606
Test: atest CtsWidgetTestCases:RemoteViewsTest
Test: atest CtsWidgetTestCases:RemoteViewsRecyclingTest
Test: atest CtsInputMethodTestCases:android.view.inputmethod.cts.InputConnectionBlockingMethodTest
Change-Id: I8924c02f7f0223458f556a07a3dfdc96b4ce612e
This commit is contained in:
Pierre Barbier de Reuille
2021-03-19 12:36:23 +00:00
parent b4a4b46b7b
commit 96059b47a9
4 changed files with 32 additions and 8 deletions

View File

@@ -520,9 +520,7 @@ public class AppWidgetHostView extends FrameLayout {
return;
}
int layoutId = rvToApply.getLayoutId();
// If our stale view has been prepared to match active, and the new
// layout matches, try recycling it
if (content == null && layoutId == mLayoutId) {
if (rvToApply.canRecycleView(mView)) {
try {
rvToApply.reapply(mContext, mView, mInteractionHandler, mCurrentSize,
mColorResources);

View File

@@ -2196,7 +2196,7 @@ public class RemoteViews implements Parcelable, Filter {
int recycledViewIndex = findViewIndexToRecycle(target, rvToApply);
if (recycledViewIndex >= 0) {
View child = target.getChildAt(recycledViewIndex);
if (getViewLayoutId(child) == rvToApply.getLayoutId()) {
if (rvToApply.canRecycleView(child)) {
if (nextChild < recycledViewIndex) {
target.removeViews(nextChild, recycledViewIndex - nextChild);
}
@@ -2254,7 +2254,7 @@ public class RemoteViews implements Parcelable, Filter {
// application are placed before.
ViewTree recycled = target.mChildren.get(recycledViewIndex);
// We can only recycle the view if the layout id is the same.
if (getViewLayoutId(recycled.mRoot) == rvToApply.getLayoutId()) {
if (rvToApply.canRecycleView(recycled.mRoot)) {
if (recycledViewIndex > nextChild) {
target.removeChildren(nextChild, recycledViewIndex - nextChild);
}
@@ -3726,7 +3726,8 @@ public class RemoteViews implements Parcelable, Filter {
*
* The {@code stableId} will be used to identify a potential view to recycled when the remote
* view is inflated. Views can be re-used if inserted in the same order, potentially with
* some views appearing / disappearing.
* some views appearing / disappearing. To be recycled the view must not change the layout
* used to inflate it or its view id (see {@link RemoteViews#setViewId}).
*
* Note: if a view is re-used, all the actions will be re-applied on it. However, its properties
* are not reset, so what was applied in previous round will have an effect. As a view may be
@@ -5116,6 +5117,7 @@ public class RemoteViews implements Parcelable, Filter {
View v = inflater.inflate(rv.getLayoutId(), parent, false);
if (mViewId != View.NO_ID) {
v.setId(mViewId);
v.setTagInternal(R.id.remote_views_override_id, mViewId);
}
v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
return v;
@@ -5335,6 +5337,24 @@ public class RemoteViews implements Parcelable, Filter {
reapply(context, v, handler, size, colorResources, true);
}
/** @hide */
public boolean canRecycleView(@Nullable View v) {
if (v == null) {
return false;
}
Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
if (previousLayoutId == null) {
return false;
}
Integer overrideIdTag = (Integer) v.getTag(R.id.remote_views_override_id);
int overrideId = overrideIdTag == null ? View.NO_ID : overrideIdTag;
// If mViewId is View.NO_ID, we only recycle if overrideId is also View.NO_ID.
// Otherwise, it might be that, on a previous iteration, the view's ID was set to
// something else, and it should now be reset to the ID defined in the XML layout file,
// whatever it is.
return previousLayoutId == getLayoutId() && mViewId == overrideId;
}
// Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
// should set it to false.
private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
@@ -5347,7 +5367,7 @@ public class RemoteViews implements Parcelable, Filter {
// (orientation or size), we throw an exception, since the layouts may be completely
// unrelated.
if (hasMultipleLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
if (!rvToApply.canRecycleView(v)) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}

View File

@@ -252,5 +252,8 @@
<item type="id" name="remote_views_next_child" />
<!-- View tag associating a view with its stable id for potential recycling. -->
<item type="id" name = "remote_views_stable_id" />
<item type="id" name="remote_views_stable_id" />
<!-- View tag associating a view with its overridden id, to ensure valid recycling only. -->
<item type="id" name="remote_views_override_id" />
</resources>

View File

@@ -4321,8 +4321,11 @@
<java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_allowed" />
<java-symbol type="bool" name="config_cecRcProfileSourceMediaContextSensitiveMenuNotHandled_default" />
<!-- Ids for RemoteViews -->
<java-symbol type="id" name="remote_views_next_child" />
<java-symbol type="id" name="remote_views_stable_id" />
<java-symbol type="id" name="remote_views_override_id" />
<!-- View and control prompt -->
<java-symbol type="drawable" name="ic_accessibility_24dp" />
<java-symbol type="string" name="view_and_control_notification_title" />