Commit b63ab0cf authored by Dan Harrington's avatar Dan Harrington Committed by Commit Bot

feed v2: fix native view handling bug in recycler

This is the chromium/src side of the change.
It introduces a getViewType() API in ListContentManager,
and implements it.

This CL fixes a secondary bug: if a native view is added, removed,
and then added again to the FeedListContentManager, we would previously
try to wrap the view multiple times, which throws an exception. Now,
we wrap a view at most once.

Bug: 1119516, b/165317997
Change-Id: I2163c0e159c6d434329ea34f0449356a561a0c96
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2364965Reviewed-by: default avatarDan H <harringtond@chromium.org>
Reviewed-by: default avatarCathy Li <chili@chromium.org>
Commit-Queue: Dan H <harringtond@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800247}
parent 5d806aed
...@@ -8,8 +8,11 @@ import android.view.LayoutInflater; ...@@ -8,8 +8,11 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import org.chromium.chrome.browser.xsurface.FeedActionsHandler; import org.chromium.chrome.browser.xsurface.FeedActionsHandler;
import org.chromium.chrome.browser.xsurface.ListContentManager; import org.chromium.chrome.browser.xsurface.ListContentManager;
import org.chromium.chrome.browser.xsurface.ListContentManagerObserver; import org.chromium.chrome.browser.xsurface.ListContentManagerObserver;
...@@ -79,9 +82,11 @@ public class FeedListContentManager implements ListContentManager { ...@@ -79,9 +82,11 @@ public class FeedListContentManager implements ListContentManager {
* For the content that is supported by the native view. * For the content that is supported by the native view.
*/ */
public static class NativeViewContent extends FeedContent { public static class NativeViewContent extends FeedContent {
private FrameLayout mEnclosingLayout;
private View mNativeView; private View mNativeView;
private int mResId; private int mResId;
// An unique ID for this NativeViewContent. This is initially 0, and assigned by
// FeedListContentManager when needed.
private int mViewType;
/** Holds an inflated native view. */ /** Holds an inflated native view. */
public NativeViewContent(String key, View nativeView) { public NativeViewContent(String key, View nativeView) {
...@@ -104,13 +109,32 @@ public class FeedListContentManager implements ListContentManager { ...@@ -104,13 +109,32 @@ public class FeedListContentManager implements ListContentManager {
mNativeView = mNativeView =
LayoutInflater.from(parent.getContext()).inflate(mResId, parent, false); LayoutInflater.from(parent.getContext()).inflate(mResId, parent, false);
} }
if (mEnclosingLayout == null) {
mEnclosingLayout = new FrameLayout(parent.getContext()); // If there's already a parent, we have already enclosed this view previously.
mEnclosingLayout.setLayoutParams( // This can happen if a native view is added, removed, and added again.
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); ViewParent nativeViewParent = mNativeView.getParent();
mEnclosingLayout.addView(mNativeView); if (nativeViewParent != null) {
assert nativeViewParent instanceof View;
return (View) nativeViewParent;
} }
return mEnclosingLayout;
FrameLayout enclosingLayout = new FrameLayout(parent.getContext());
enclosingLayout.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
enclosingLayout.addView(mNativeView);
return enclosingLayout;
}
int getViewType() {
return mViewType;
}
void setViewType(int viewType) {
mViewType = viewType;
}
public View getNestedView() {
return mNativeView;
} }
@Override @Override
...@@ -122,6 +146,7 @@ public class FeedListContentManager implements ListContentManager { ...@@ -122,6 +146,7 @@ public class FeedListContentManager implements ListContentManager {
private ArrayList<FeedContent> mFeedContentList; private ArrayList<FeedContent> mFeedContentList;
private ArrayList<ListContentManagerObserver> mObservers; private ArrayList<ListContentManagerObserver> mObservers;
private final Map<String, Object> mHandlers; private final Map<String, Object> mHandlers;
private int mPreviousViewType;
FeedListContentManager( FeedListContentManager(
SurfaceActionsHandler surfaceActionsHandler, FeedActionsHandler feedActionsHandler) { SurfaceActionsHandler surfaceActionsHandler, FeedActionsHandler feedActionsHandler) {
...@@ -251,10 +276,18 @@ public class FeedListContentManager implements ListContentManager { ...@@ -251,10 +276,18 @@ public class FeedListContentManager implements ListContentManager {
} }
@Override @Override
public View getNativeView(int index, ViewGroup parent) { public int getViewType(int position) {
assert mFeedContentList.get(index).isNativeView(); assert mFeedContentList.get(position).isNativeView();
NativeViewContent nativeViewContent = (NativeViewContent) mFeedContentList.get(index); NativeViewContent content = (NativeViewContent) mFeedContentList.get(position);
return nativeViewContent.getNativeView(parent); if (content.getViewType() == 0) content.setViewType(++mPreviousViewType);
return content.getViewType();
}
@Override
public View getNativeView(int viewType, ViewGroup parent) {
NativeViewContent viewContent = findNativeViewByType(viewType);
assert viewContent != null;
return viewContent.getNativeView(parent);
} }
@Override @Override
...@@ -276,4 +309,17 @@ public class FeedListContentManager implements ListContentManager { ...@@ -276,4 +309,17 @@ public class FeedListContentManager implements ListContentManager {
public void removeObserver(ListContentManagerObserver observer) { public void removeObserver(ListContentManagerObserver observer) {
mObservers.remove(observer); mObservers.remove(observer);
} }
@Nullable
private NativeViewContent findNativeViewByType(int viewType) {
// Note: since there's relatively few native views, they're mostly at the front, a linear
// search isn't terrible. This function is also called infrequently.
for (int i = 0; i < mFeedContentList.size(); i++) {
FeedContent item = mFeedContentList.get(i);
if (!item.isNativeView()) continue;
NativeViewContent nativeContent = (NativeViewContent) item;
if (nativeContent.getViewType() == viewType) return nativeContent;
}
return null;
}
} }
...@@ -55,11 +55,9 @@ public class NativeViewListRenderer extends RecyclerView.Adapter<NativeViewListR ...@@ -55,11 +55,9 @@ public class NativeViewListRenderer extends RecyclerView.Adapter<NativeViewListR
@Override @Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// viewType is same as position.
int position = viewType;
View v; View v;
if (mManager.isNativeView(position)) { if (viewType >= 0) {
v = mManager.getNativeView(position, parent); v = mManager.getNativeView(viewType, parent);
} else { } else {
TextView textView = new TextView(ContextUtils.getApplicationContext()); TextView textView = new TextView(ContextUtils.getApplicationContext());
String message = "Unable to render external view"; String message = "Unable to render external view";
...@@ -71,7 +69,8 @@ public class NativeViewListRenderer extends RecyclerView.Adapter<NativeViewListR ...@@ -71,7 +69,8 @@ public class NativeViewListRenderer extends RecyclerView.Adapter<NativeViewListR
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return position; if (!mManager.isNativeView(position)) return -1;
return mManager.getViewType(position);
} }
/* HybridListRenderer methods */ /* HybridListRenderer methods */
......
...@@ -14,6 +14,7 @@ import android.app.Activity; ...@@ -14,6 +14,7 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest;
import android.view.View; import android.view.View;
import android.view.ViewParent;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.LinearLayout; import android.widget.LinearLayout;
...@@ -196,10 +197,52 @@ public class FeedListContentManagerTest implements ListContentManagerObserver { ...@@ -196,10 +197,52 @@ public class FeedListContentManagerTest implements ListContentManagerObserver {
assertTrue(mManager.isNativeView(4)); assertTrue(mManager.isNativeView(4));
assertArrayEquals("foo".getBytes(), mManager.getExternalViewBytes(0)); assertArrayEquals("foo".getBytes(), mManager.getExternalViewBytes(0));
assertEquals(v2, getNativeView(1)); assertEquals(v2, getNativeView(mManager.getViewType(1)));
assertEquals(v3, getNativeView(2)); assertEquals(v3, getNativeView(mManager.getViewType(2)));
assertArrayEquals("hello".getBytes(), mManager.getExternalViewBytes(3)); assertArrayEquals("hello".getBytes(), mManager.getExternalViewBytes(3));
assertEquals(v5, getNativeView(4)); assertEquals(v5, getNativeView(mManager.getViewType(4)));
}
@Test
@SmallTest
public void testGetViewDataCreatesEnclosingViewOnce() {
View v = new View(mContext);
FeedListContentManager.FeedContent c = createNativeViewContent(v);
addContents(0, ImmutableList.of(c));
assertEquals(v, getNativeView(mManager.getViewType(0)));
ViewParent p = v.getParent();
// Remove and re-add the same view, but with a new NativeViewContent.
// This time, getNativeView() does not need to create the enclosing parent view.
removeContents(0, 1);
c = createNativeViewContent(v);
addContents(0, ImmutableList.of(c));
assertEquals(v, getNativeView(mManager.getViewType(0)));
assertEquals(p, v.getParent());
}
@Test
@SmallTest
public void testGetNativeViewAfterMove() {
View v1 = new View(mContext);
FeedListContentManager.FeedContent c1 = createNativeViewContent(v1);
View v0 = new View(mContext);
FeedListContentManager.FeedContent c0 = createNativeViewContent(v0);
addContents(0, ImmutableList.of(c1));
int t1 = mManager.getViewType(0);
addContents(0, ImmutableList.of(c0));
int t0 = mManager.getViewType(0);
assertEquals(1, t1);
assertEquals(2, t0);
assertEquals(t0, mManager.getViewType(0));
assertEquals(t1, mManager.getViewType(1));
assertEquals(v0, getNativeView(t0));
assertEquals(v1, getNativeView(t1));
} }
@Override @Override
...@@ -278,8 +321,8 @@ public class FeedListContentManagerTest implements ListContentManagerObserver { ...@@ -278,8 +321,8 @@ public class FeedListContentManagerTest implements ListContentManagerObserver {
return new FeedListContentManager.NativeViewContent(v.toString(), v); return new FeedListContentManager.NativeViewContent(v.toString(), v);
} }
private View getNativeView(int index) { private View getNativeView(int viewType) {
View view = mManager.getNativeView(index, mParent); View view = mManager.getNativeView(viewType, mParent);
assertNotNull(view); assertNotNull(view);
assertTrue(view instanceof FrameLayout); assertTrue(view instanceof FrameLayout);
return ((FrameLayout) view).getChildAt(0); return ((FrameLayout) view).getChildAt(0);
......
...@@ -40,10 +40,21 @@ public interface ListContentManager { ...@@ -40,10 +40,21 @@ public interface ListContentManager {
* View should not be attached to parent. {@link bindNativeView} will * View should not be attached to parent. {@link bindNativeView} will
* be called later to attach more information to the view. * be called later to attach more information to the view.
*/ */
default View getNativeView(int index, ViewGroup parent) { default View getNativeView(int viewType, ViewGroup parent) {
return null; return null;
} }
/**
* Returns the view type for item at position.
*
* The view type is later passed to getNativeView to attach the view.
* This will only be called when isNativeView(position) is true.
*/
default int getViewType(int position) {
// This is an incorrect, but backwards compatible implementation.
return position;
}
/** /**
* Binds the data at the specified location. * Binds the data at the specified location.
*/ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment