Merge "Updated support for animator in MediaNowPlayingView pre-Lollipop" into nyc-mr1-dev
diff --git a/build.gradle b/build.gradle
index 9d02725..258d4d0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,8 +30,8 @@
doclava project(':doclava')
}
-ext.supportVersion = '25.0.0-SNAPSHOT'
-ext.extraVersion = 36
+ext.supportVersion = '25.0.0'
+ext.extraVersion = 39
ext.supportRepoOut = ''
ext.buildToolsVersion = '23.0.2'
ext.buildNumber = Integer.toString(ext.extraVersion)
diff --git a/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
index 0a6e158..9de02b2 100644
--- a/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
+++ b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
@@ -20,6 +20,7 @@
import android.os.SystemClock;
import android.support.annotation.RestrictTo;
import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewCompat;
import android.support.v7.view.menu.ShowableListMenu;
import android.view.MotionEvent;
import android.view.View;
@@ -99,12 +100,12 @@
private void addDetachListenerBase(View src) {
src.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
- boolean mIsAttached = mSrc.isAttachedToWindow();
+ boolean mIsAttached = ViewCompat.isAttachedToWindow(mSrc);
@Override
public void onGlobalLayout() {
final boolean wasAttached = mIsAttached;
- mIsAttached = mSrc.isAttachedToWindow();
+ mIsAttached = ViewCompat.isAttachedToWindow(mSrc);
if (wasAttached && !mIsAttached) {
onDetachedFromWindow();
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AppCompatVectorDrawableIntegrationTest.java b/v7/appcompat/tests/src/android/support/v7/app/AppCompatVectorDrawableIntegrationTest.java
index b021ade..905521d 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/AppCompatVectorDrawableIntegrationTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/AppCompatVectorDrawableIntegrationTest.java
@@ -73,8 +73,9 @@
assertEquals("Left side should be white", Color.red(leftColor), 255);
assertEquals("Right side should be black", Color.red(rightColor), 0);
- if (Build.VERSION.SDK_INT >= 17) {
- // setLayoutDirection is only available after API 17.
+ if (Build.VERSION.SDK_INT >= 19) {
+ // setLayoutDirection is only available after API 17. However, it correctly set its
+ // drawable's layout direction until API 19.
view1.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
vectorDrawable.draw(mCanvas);
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index 20e83aa..25288c0 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -513,6 +513,9 @@
}
}
}
+
+ setEnabled(mRouter.isRouteAvailable(mSelector,
+ MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
}
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index a70c5de..4eb81a1 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -5903,6 +5903,20 @@
}
}
+ boolean isPrefetchPositionAttached(int position) {
+ final int childCount = mChildHelper.getUnfilteredChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View attachedView = mChildHelper.getUnfilteredChildAt(i);
+ ViewHolder holder = getChildViewHolderInt(attachedView);
+ // TODO: consider ignoring if holder isInvalid
+ // Note: can use mPosition here because adapter doesn't have pending updates
+ if (holder.mPosition == position) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void prefetch(int[] itemPrefetchArray, int viewCount) {
if (viewCount == 0) return;
@@ -5911,11 +5925,18 @@
throw new IllegalArgumentException("Recycler requested to prefetch invalid view "
+ childPosition);
}
- View prefetchView = getViewForPosition(childPosition);
+
+ View prefetchView = null;
+ if (!isPrefetchPositionAttached(childPosition)) {
+ // only prefetch if child not already attached
+ prefetchView = getViewForPosition(childPosition);
+ }
if (viewCount > 1) {
prefetch(itemPrefetchArray, viewCount - 1);
}
- recycleView(prefetchView);
+ if (prefetchView != null) {
+ recycleView(prefetchView);
+ }
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index f0225d3..0d538ac 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -16,6 +16,9 @@
package android.support.v7.widget;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
@@ -62,6 +65,13 @@
return InstrumentationRegistry.getContext();
}
+ private void layout(int width, int height) {
+ mRecyclerView.measure(
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));
+ mRecyclerView.layout(0, 0, width, height);
+ }
+
@Test
public void prefetchReusesCacheItems() {
RecyclerView.LayoutManager prefetchingLayoutManager = new RecyclerView.LayoutManager() {
@@ -102,8 +112,7 @@
when(mockAdapter.getItemCount()).thenReturn(10);
mRecyclerView.setAdapter(mockAdapter);
- mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 240);
- mRecyclerView.layout(0, 0, 320, 320);
+ layout(320, 320);
verify(mockAdapter, never()).onCreateViewHolder(any(ViewGroup.class), anyInt());
verify(mockAdapter, never()).onBindViewHolder(
@@ -127,6 +136,13 @@
}
}
+ private void verifyCacheDoesNotContainPosition(int position) {
+ for (int i = 0; i < mRecycler.mCachedViews.size(); i++) {
+ assertNotEquals("Cache must not contain position " + position,
+ position, mRecycler.mCachedViews.get(i).mPosition);
+ }
+ }
+
private void verifyCacheContainsPosition(int position) {
for (int i = 0; i < mRecycler.mCachedViews.size(); i++) {
if (mRecycler.mCachedViews.get(i).mPosition == position) return;
@@ -150,43 +166,43 @@
@Override
public RecyclerView.ViewHolder answer(InvocationOnMock invocation)
throws Throwable {
- return new RecyclerView.ViewHolder(new View(getContext())) {};
+ View view = new View(getContext());
+ view.setMinimumWidth(100);
+ view.setMinimumHeight(100);
+ return new RecyclerView.ViewHolder(view) {};
}
});
when(mockAdapter.getItemCount()).thenReturn(100);
mRecyclerView.setAdapter(mockAdapter);
+ layout(300, 100);
- mRecyclerView.measure(View.MeasureSpec.AT_MOST | 320, View.MeasureSpec.AT_MOST | 320);
- mRecyclerView.layout(0, 0, 320, 320);
-
- mViewPrefetcher.mItemPrefetchArray = new int[] { 0, 1, 2 };
+ mViewPrefetcher.mItemPrefetchArray = new int[] { 3, 4, 5 };
mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, 3);
- verifyCacheContainsPositions(0, 1, 2);
+ verifyCacheContainsPositions(3, 4, 5);
// further views recycled, as though from scrolling, shouldn't evict prefetched views:
mRecycler.recycleView(mRecycler.getViewForPosition(10));
- verifyCacheContainsPositions(0, 1, 2, 10);
+ verifyCacheContainsPositions(3, 4, 5, 10);
mRecycler.recycleView(mRecycler.getViewForPosition(20));
- verifyCacheContainsPositions(0, 1, 2, 10, 20);
+ verifyCacheContainsPositions(3, 4, 5, 10, 20);
mRecycler.recycleView(mRecycler.getViewForPosition(30));
- verifyCacheContainsPositions(0, 1, 2, 20, 30);
+ verifyCacheContainsPositions(3, 4, 5, 20, 30);
mRecycler.recycleView(mRecycler.getViewForPosition(40));
- verifyCacheContainsPositions(0, 1, 2, 30, 40);
+ verifyCacheContainsPositions(3, 4, 5, 30, 40);
// After clearing the cache, the prefetch priorities should be cleared as well:
mRecyclerView.mRecycler.recycleAndClearCachedViews();
- for (int i : new int[] {0, 1, 2, 50, 60, 70, 80, 90}) {
+ for (int i : new int[] {3, 4, 5, 50, 60, 70, 80, 90}) {
mRecycler.recycleView(mRecycler.getViewForPosition(i));
}
// cache only contains most recent positions, no priority for previous prefetches:
verifyCacheContainsPositions(50, 60, 70, 80, 90);
-
}
@Test
@@ -212,8 +228,7 @@
// NOTE: requested cache size must be smaller than span count so two rows cannot fit
mRecyclerView.setItemViewCacheSize(2);
- mRecyclerView.measure(View.MeasureSpec.AT_MOST | 300, View.MeasureSpec.AT_MOST | 200);
- mRecyclerView.layout(0, 0, 300, 150);
+ layout(300, 150);
mRecyclerView.scrollBy(0, 75);
assertTrue(mRecycler.mCachedViews.isEmpty());
@@ -234,4 +249,67 @@
verifyCacheContainsPositions(9, 10, 11);
assertTrue(mRecycler.mCachedViews.size() == 5);
}
+
+ @Test
+ public void prefetchItemsSkipAnimations() {
+ LinearLayoutManager llm = new LinearLayoutManager(getContext());
+ mRecyclerView.setLayoutManager(llm);
+ final int[] expandedPosition = new int[] {-1};
+
+ final RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new RecyclerView.ViewHolder(new View(parent.getContext())) {};
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ int height = expandedPosition[0] == position ? 400 : 100;
+ holder.itemView.setLayoutParams(new RecyclerView.LayoutParams(200, height));
+ }
+
+ @Override
+ public int getItemCount() { return 10; }
+ };
+
+ // make move duration long enough to be able to see the effects
+ RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
+ itemAnimator.setMoveDuration(10000);
+ mRecyclerView.setAdapter(adapter);
+
+ layout(200, 400);
+
+ expandedPosition[0] = 1;
+ // insert payload to avoid creating a new view
+ adapter.notifyItemChanged(1, new Object());
+
+ layout(200, 400);
+ layout(200, 400);
+
+ assertTrue(itemAnimator.isRunning());
+ assertEquals(2, llm.getChildCount());
+ assertEquals(4, mRecyclerView.getChildCount());
+
+ // animating view should be observable as hidden, uncached...
+ verifyCacheDoesNotContainPosition(2);
+ assertNotNull("Animating view should be found, hidden",
+ mRecyclerView.mChildHelper.findHiddenNonRemovedView(2, RecyclerView.INVALID_TYPE));
+
+ // ...but must not be removed for prefetch
+ mViewPrefetcher.mItemPrefetchArray = new int[] {-1};
+ int viewCount = mRecyclerView.getLayoutManager().gatherPrefetchIndices(0, 1,
+ mRecyclerView.mState, mViewPrefetcher.mItemPrefetchArray);
+ mRecycler.prefetch(mViewPrefetcher.mItemPrefetchArray, viewCount);
+ int prefetchTarget = mViewPrefetcher.mItemPrefetchArray[0];
+ assertEquals("Prefetch must target view 2", 2, prefetchTarget);
+
+ // animating view still observable as hidden, uncached
+ verifyCacheDoesNotContainPosition(2);
+ assertNotNull("Animating view should be found, hidden",
+ mRecyclerView.mChildHelper.findHiddenNonRemovedView(2, RecyclerView.INVALID_TYPE));
+
+ assertTrue(itemAnimator.isRunning());
+ assertEquals(2, llm.getChildCount());
+ assertEquals(4, mRecyclerView.getChildCount());
+ }
}
\ No newline at end of file