Merge "Relayout when base inner insets change" into oc-support-26.0-dev
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index 5e565a1..657fba6 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -8290,6 +8290,14 @@
}
+package android.support.v4.utils {
+
+ public class ObjectUtils {
+ method public static boolean objectEquals(java.lang.Object, java.lang.Object);
+ }
+
+}
+
package android.support.v4.view {
public abstract class AbsSavedState implements android.os.Parcelable {
@@ -10139,10 +10147,6 @@
method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
}
- public deprecated class ActionBarActivity extends android.support.v7.app.AppCompatActivity {
- ctor public ActionBarActivity();
- }
-
public class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, int, int);
ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, android.support.v7.widget.Toolbar, int, int);
@@ -12504,6 +12508,8 @@
method public boolean didStructureChange();
method public <T> T get(int);
method public int getItemCount();
+ method public int getRemainingScrollHorizontal();
+ method public int getRemainingScrollVertical();
method public int getTargetScrollPosition();
method public boolean hasTargetScrollPosition();
method public boolean isMeasuring();
diff --git a/compat/java/android/support/v4/view/ViewCompat.java b/compat/java/android/support/v4/view/ViewCompat.java
index f10ac1d..efbf848 100644
--- a/compat/java/android/support/v4/view/ViewCompat.java
+++ b/compat/java/android/support/v4/view/ViewCompat.java
@@ -2446,7 +2446,7 @@
/**
* Returns the minimum width of the view.
*
- * <p>Prior to API 16 this will return 0.</p>
+ * <p>Prior to API 16, this method may return 0 on some platforms.</p>
*
* @return the minimum width the view will try to be.
*/
@@ -2457,7 +2457,7 @@
/**
* Returns the minimum height of the view.
*
- * <p>Prior to API 16 this will return 0.</p>
+ * <p>Prior to API 16, this method may return 0 on some platforms.</p>
*
* @return the minimum height the view will try to be.
*/
@@ -2469,8 +2469,6 @@
* This method returns a ViewPropertyAnimator object, which can be used to animate
* specific properties on this View.
*
- * <p>Prior to API 14, this method will do nothing.</p>
- *
* @return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.
*/
public static ViewPropertyAnimatorCompat animate(View view) {
diff --git a/core-utils/java/android/support/v4/utils/ObjectUtils.java b/core-utils/java/android/support/v4/utils/ObjectUtils.java
new file mode 100644
index 0000000..cd4afc7
--- /dev/null
+++ b/core-utils/java/android/support/v4/utils/ObjectUtils.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package android.support.v4.utils;
+
+/**
+ * This class consists of static utility methods for operating on objects.
+ */
+public class ObjectUtils {
+ /**
+ * Returns true if the arguments are equal to each other and false otherwise.
+ * @param a an object
+ * @param b an object to be compared with <code>a</code> for equality
+ * @return <code>true</code> if the arguments are equal to each other and
+ * <code>false</code> otherwise.
+ */
+ public static boolean objectEquals(Object a, Object b) {
+ return (a == b) || (a != null && a.equals(b));
+ }
+
+ private ObjectUtils() {
+ }
+}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 1b0b589..89fcf7c 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -17,7 +17,7 @@
package android.support.design.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import android.animation.ValueAnimator;
import android.content.Context;
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index c3d1268..100b0dc 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -17,7 +17,7 @@
package android.support.design.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import android.animation.ValueAnimator;
import android.content.Context;
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 5ee3b32..e5c52b3 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -17,7 +17,7 @@
package android.support.design.widget;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static android.support.design.widget.ViewUtils.objectEquals;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import android.content.Context;
import android.content.res.Resources;
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index bd02bc3..52efdde 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -64,6 +64,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewStructure;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateInterpolator;
import android.widget.EditText;
@@ -121,6 +122,7 @@
private final FrameLayout mInputFrame;
EditText mEditText;
+ private CharSequence mOriginalHint;
private boolean mHintEnabled;
private CharSequence mHint;
@@ -313,6 +315,24 @@
return mTypeface;
}
+ @Override
+ public void dispatchProvideAutofillStructure(ViewStructure structure, int flags) {
+ if (mOriginalHint == null || mEditText == null) {
+ super.dispatchProvideAutofillStructure(structure, flags);
+ return;
+ }
+
+ // Temporarily sets child's hint to its original value so it is properly set in the
+ // child's ViewStructure.
+ final CharSequence hint = mEditText.getHint();
+ mEditText.setHint(mOriginalHint);
+ try {
+ super.dispatchProvideAutofillStructure(structure, flags);
+ } finally {
+ mEditText.setHint(hint);
+ }
+ }
+
private void setEditText(EditText editText) {
// If we already have an EditText, throw an exception
if (mEditText != null) {
@@ -364,7 +384,9 @@
// If we do not have a valid hint, try and retrieve it from the EditText, if enabled
if (mHintEnabled && TextUtils.isEmpty(mHint)) {
- setHint(mEditText.getHint());
+ // Save the hint so it can be restored on dispatchProvideAutofillStructure();
+ mOriginalHint = mEditText.getHint();
+ setHint(mOriginalHint);
// Clear the EditText's hint as we will display it ourselves
mEditText.setHint(null);
}
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index 1762b09..c09eac1 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -19,10 +19,6 @@
import android.graphics.PorterDuff;
class ViewUtils {
- static boolean objectEquals(Object a, Object b) {
- return (a == b) || (a != null && a.equals(b));
- }
-
static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
switch (value) {
case 3:
diff --git a/design/tests/src/android/support/design/testutils/ViewStructureImpl.java b/design/tests/src/android/support/design/testutils/ViewStructureImpl.java
new file mode 100644
index 0000000..c7369e9
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/ViewStructureImpl.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+package android.support.design.testutils;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.LocaleList;
+import android.view.ViewStructure;
+import android.view.ViewStructure.HtmlInfo.Builder;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+/**
+ * Simple implementation of {@link ViewStructure} that's easier to use than a Mockito mock.
+ *
+ * <p>Currently supports only {@code hint}, {@code className}, and child-related methods.
+ */
+public class ViewStructureImpl extends ViewStructure {
+
+ private CharSequence mHint;
+ private String mClassName;
+ private ViewStructureImpl[] mChildren;
+
+ @Override
+ public void setHint(CharSequence hint) {
+ mHint = hint;
+ }
+
+ // Supported methods
+ @Override
+ public CharSequence getHint() {
+ return mHint;
+ }
+
+ @Override
+ public void setChildCount(int num) {
+ mChildren = new ViewStructureImpl[num];
+ }
+
+ @Override
+ public void setClassName(String className) {
+ mClassName = className;
+ }
+
+ public String getClassName() {
+ return mClassName;
+ }
+
+ @Override
+ public int addChildCount(int num) {
+ if (mChildren == null) {
+ setChildCount(num);
+ return 0;
+ }
+ final int start = mChildren.length;
+ ViewStructureImpl[] newArray = new ViewStructureImpl[start + num];
+ System.arraycopy(mChildren, 0, newArray, 0, start);
+ mChildren = newArray;
+ return start;
+ }
+
+ @Override
+ public int getChildCount() {
+ if (mChildren == null) {
+ return 0;
+ }
+ return mChildren.length;
+ }
+
+ public ViewStructureImpl getChildAt(int index) {
+ return mChildren[index];
+ }
+
+ @Override
+ public ViewStructure newChild(int index) {
+ final ViewStructureImpl child = new ViewStructureImpl();
+ mChildren[index] = child;
+ return child;
+ }
+
+ @Override
+ public ViewStructure asyncNewChild(int index) {
+ return newChild(index);
+ }
+
+
+ // Unsupported methods
+ @Override
+ public void setId(int id, String packageName, String typeName, String entryName) {
+ }
+
+ @Override
+ public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {
+ }
+
+ @Override
+ public void setTransformation(Matrix matrix) {
+ }
+
+ @Override
+ public void setElevation(float elevation) {
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ }
+
+ public void setAssistBlocked(boolean state) {
+ }
+
+ @Override
+ public void setEnabled(boolean state) {
+ }
+
+ @Override
+ public void setClickable(boolean state) {
+ }
+
+ @Override
+ public void setLongClickable(boolean state) {
+ }
+
+ @Override
+ public void setContextClickable(boolean state) {
+ }
+
+ @Override
+ public void setFocusable(boolean state) {
+ }
+
+ @Override
+ public void setFocused(boolean state) {
+ }
+
+ @Override
+ public void setAccessibilityFocused(boolean state) {
+ }
+
+ @Override
+ public void setCheckable(boolean state) {
+ }
+
+ @Override
+ public void setChecked(boolean state) {
+ }
+
+ @Override
+ public void setSelected(boolean state) {
+ }
+
+ @Override
+ public void setActivated(boolean state) {
+ }
+
+ @Override
+ public void setOpaque(boolean opaque) {
+ }
+
+ @Override
+ public void setContentDescription(CharSequence contentDescription) {
+ }
+
+ @Override
+ public void setText(CharSequence text) {
+ }
+
+ @Override
+ public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+ }
+
+ @Override
+ public void setTextStyle(float size, int fgColor, int bgColor, int style) {
+ }
+
+ @Override
+ public void setTextLines(int[] charOffsets, int[] baselines) {
+ }
+
+ @Override
+ public CharSequence getText() {
+ return null;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return 0;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return 0;
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return null;
+ }
+
+ @Override
+ public boolean hasExtras() {
+ return false;
+ }
+
+ @Override
+ public AutofillId getAutofillId() {
+ return null;
+ }
+
+ @Override
+ public void setAutofillId(AutofillId id) {
+ }
+
+ public void setAutofillId(ViewStructure parent, int virtualId) {
+ }
+
+ @Override
+ public void setAutofillId(AutofillId parentId, int virtualId) {
+ }
+
+ @Override
+ public void setAutofillType(int type) {
+ }
+
+ @Override
+ public void setAutofillHints(String[] hint) {
+ }
+
+ @Override
+ public void setAutofillValue(AutofillValue value) {
+ }
+
+ @Override
+ public void setAutofillOptions(CharSequence[] options) {
+ }
+
+ @Override
+ public void setInputType(int inputType) {
+ }
+
+ @Override
+ public void setDataIsSensitive(boolean sensitive) {
+ }
+
+ @Override
+ public void asyncCommit() {
+ }
+
+ public Rect getTempRect() {
+ return null;
+ }
+
+ @Override
+ public void setWebDomain(String domain) {
+ }
+
+ @Override
+ public void setLocaleList(LocaleList localeList) {
+ }
+
+ @Override
+ public Builder newHtmlInfoBuilder(String tagName) {
+ return null;
+ }
+
+ @Override
+ public void setHtmlInfo(HtmlInfo htmlInfo) {
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
index 929fcd7..226463c 100755
--- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -49,6 +49,7 @@
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
@@ -58,13 +59,16 @@
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.Parcelable;
import android.support.design.test.R;
import android.support.design.testutils.TestUtils;
+import android.support.design.testutils.ViewStructureImpl;
import android.support.test.annotation.UiThreadTest;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewAssertion;
import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
import android.support.v4.widget.TextViewCompat;
import android.util.AttributeSet;
import android.util.SparseArray;
@@ -304,6 +308,30 @@
assertEquals(INPUT_TEXT, info.hintText);
}
+ @UiThreadTest
+ @Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+ public void testDispatchProvideAutofillStructure() {
+ final Activity activity = mActivityTestRule.getActivity();
+
+ final TextInputLayout layout = activity.findViewById(R.id.textinput);
+
+ final ViewStructureImpl structure = new ViewStructureImpl();
+ layout.dispatchProvideAutofillStructure(structure, 0);
+
+ assertEquals(2, structure.getChildCount()); // EditText and TextView
+
+ // Asserts the structure.
+ final ViewStructureImpl childStructure = structure.getChildAt(0);
+ assertEquals(EditText.class.getName(), childStructure.getClassName());
+ assertEquals("Hint to the user", childStructure.getHint());
+
+ // Make sure the widget's hint was restored.
+ assertEquals("Hint to the user", layout.getHint());
+ final EditText editText = activity.findViewById(R.id.textinput_edittext);
+ assertNull(editText.getHint());
+ }
+
/**
* Regression test for b/31663756.
*/
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 9f92d30..c49b69b 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -652,7 +652,7 @@
int mNextFragmentIndex = 0;
- ArrayList<Fragment> mAdded;
+ final ArrayList<Fragment> mAdded = new ArrayList<>();
SparseArray<Fragment> mActive;
ArrayList<BackStackRecord> mBackStack;
ArrayList<Fragment> mCreatedMenus;
@@ -908,7 +908,7 @@
@Override
public List<Fragment> getFragments() {
- if (mAdded == null) {
+ if (mAdded.isEmpty()) {
return Collections.EMPTY_LIST;
}
synchronized (mAdded) {
@@ -1001,15 +1001,16 @@
}
}
- if (mAdded != null) {
- N = mAdded.size();
- if (N > 0) {
- writer.print(prefix); writer.println("Added Fragments:");
- for (int i=0; i<N; i++) {
- Fragment f = mAdded.get(i);
- writer.print(prefix); writer.print(" #"); writer.print(i);
- writer.print(": "); writer.println(f.toString());
- }
+ N = mAdded.size();
+ if (N > 0) {
+ writer.print(prefix); writer.println("Added Fragments:");
+ for (int i = 0; i < N; i++) {
+ Fragment f = mAdded.get(i);
+ writer.print(prefix);
+ writer.print(" #");
+ writer.print(i);
+ writer.print(": ");
+ writer.println(f.toString());
}
}
@@ -1801,14 +1802,12 @@
boolean loadersRunning = false;
// Must add them in the proper order. mActive fragments may be out of order
- if (mAdded != null) {
- final int numAdded = mAdded.size();
- for (int i = 0; i < numAdded; i++) {
- Fragment f = mAdded.get(i);
- moveFragmentToExpectedState(f);
- if (f.mLoaderManager != null) {
- loadersRunning |= f.mLoaderManager.hasRunningLoaders();
- }
+ final int numAdded = mAdded.size();
+ for (int i = 0; i < numAdded; i++) {
+ Fragment f = mAdded.get(i);
+ moveFragmentToExpectedState(f);
+ if (f.mLoaderManager != null) {
+ loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
@@ -1875,9 +1874,6 @@
}
public void addFragment(Fragment fragment, boolean moveToStateNow) {
- if (mAdded == null) {
- mAdded = new ArrayList<Fragment>();
- }
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment);
if (!fragment.mDetached) {
@@ -1905,10 +1901,8 @@
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
- if (mAdded != null) {
- synchronized (mAdded) {
- mAdded.remove(fragment);
- }
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
@@ -1956,11 +1950,9 @@
fragment.mDetached = true;
if (fragment.mAdded) {
// We are not already in back stack, so need to remove the fragment.
- if (mAdded != null) {
- if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
- synchronized (mAdded) {
- mAdded.remove(fragment);
- }
+ if (DEBUG) Log.v(TAG, "remove from detach: " + fragment);
+ synchronized (mAdded) {
+ mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
@@ -1975,9 +1967,6 @@
if (fragment.mDetached) {
fragment.mDetached = false;
if (!fragment.mAdded) {
- if (mAdded == null) {
- mAdded = new ArrayList<Fragment>();
- }
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
@@ -1995,13 +1984,11 @@
@Override
public Fragment findFragmentById(int id) {
- if (mAdded != null) {
- // First look through added fragments.
- for (int i=mAdded.size()-1; i>=0; i--) {
- Fragment f = mAdded.get(i);
- if (f != null && f.mFragmentId == id) {
- return f;
- }
+ // First look through added fragments.
+ for (int i = mAdded.size() - 1; i >= 0; i--) {
+ Fragment f = mAdded.get(i);
+ if (f != null && f.mFragmentId == id) {
+ return f;
}
}
if (mActive != null) {
@@ -2018,7 +2005,7 @@
@Override
public Fragment findFragmentByTag(String tag) {
- if (mAdded != null && tag != null) {
+ if (tag != null) {
// First look through added fragments.
for (int i=mAdded.size()-1; i>=0; i--) {
Fragment f = mAdded.get(i);
@@ -2358,9 +2345,7 @@
} else {
mTmpAddedFragments.clear();
}
- if (mAdded != null) {
- mTmpAddedFragments.addAll(mAdded);
- }
+ mTmpAddedFragments.addAll(mAdded);
Fragment oldPrimaryNav = getPrimaryNavigationFragment();
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
@@ -2608,7 +2593,7 @@
}
// We want to leave the fragment in the started state
final int state = Math.min(mCurState, Fragment.STARTED);
- final int numAdded = mAdded == null ? 0 : mAdded.size();
+ final int numAdded = mAdded.size();
for (int i = 0; i < numAdded; i++) {
Fragment fragment = mAdded.get(i);
if (fragment.mState < state) {
@@ -2972,18 +2957,18 @@
BackStackState[] backStack = null;
// Build list of currently added fragments.
- if (mAdded != null) {
- N = mAdded.size();
- if (N > 0) {
- added = new int[N];
- for (int i=0; i<N; i++) {
- added[i] = mAdded.get(i).mIndex;
- if (added[i] < 0) {
- throwException(new IllegalStateException(
- "Failure saving state: active " + mAdded.get(i)
- + " has cleared index: " + added[i]));
- }
- if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+ N = mAdded.size();
+ if (N > 0) {
+ added = new int[N];
+ for (int i = 0; i < N; i++) {
+ added[i] = mAdded.get(i).mIndex;
+ if (added[i] < 0) {
+ throwException(new IllegalStateException(
+ "Failure saving state: active " + mAdded.get(i)
+ + " has cleared index: " + added[i]));
+ }
+ if (DEBUG) {
+ Log.v(TAG, "saveAllState: adding fragment #" + i
+ ": " + mAdded.get(i));
}
}
@@ -3093,8 +3078,8 @@
}
// Build the list of currently added fragments.
+ mAdded.clear();
if (fms.mAdded != null) {
- mAdded = new ArrayList<Fragment>(fms.mAdded.length);
for (int i=0; i<fms.mAdded.length; i++) {
Fragment f = mActive.get(fms.mAdded[i]);
if (f == null) {
@@ -3110,8 +3095,6 @@
mAdded.add(f);
}
}
- } else {
- mAdded = null;
}
// Build the back stack.
@@ -3168,7 +3151,7 @@
public void noteStateNotSaved() {
mSavedNonConfig = null;
mStateSaved = false;
- final int addedCount = mAdded == null ? 0 : mAdded.size();
+ final int addedCount = mAdded.size();
for (int i = 0; i < addedCount; i++) {
Fragment fragment = mAdded.get(i);
if (fragment != null) {
@@ -3238,9 +3221,6 @@
}
public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final android.support.v4.app.Fragment f = mAdded.get(i);
if (f != null) {
@@ -3250,9 +3230,6 @@
}
public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
- if (mAdded == null) {
- return;
- }
for (int i = mAdded.size() - 1; i >= 0; --i) {
final android.support.v4.app.Fragment f = mAdded.get(i);
if (f != null) {
@@ -3262,23 +3239,19 @@
}
public void dispatchConfigurationChanged(Configuration newConfig) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performConfigurationChanged(newConfig);
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performConfigurationChanged(newConfig);
}
}
}
public void dispatchLowMemory() {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performLowMemory();
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performLowMemory();
}
}
}
@@ -3286,17 +3259,15 @@
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
boolean show = false;
ArrayList<Fragment> newMenus = null;
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performCreateOptionsMenu(menu, inflater)) {
- show = true;
- if (newMenus == null) {
- newMenus = new ArrayList<Fragment>();
- }
- newMenus.add(f);
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performCreateOptionsMenu(menu, inflater)) {
+ show = true;
+ if (newMenus == null) {
+ newMenus = new ArrayList<Fragment>();
}
+ newMenus.add(f);
}
}
}
@@ -3317,13 +3288,11 @@
public boolean dispatchPrepareOptionsMenu(Menu menu) {
boolean show = false;
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performPrepareOptionsMenu(menu)) {
- show = true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performPrepareOptionsMenu(menu)) {
+ show = true;
}
}
}
@@ -3331,13 +3300,11 @@
}
public boolean dispatchOptionsItemSelected(MenuItem item) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performOptionsItemSelected(item)) {
- return true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performOptionsItemSelected(item)) {
+ return true;
}
}
}
@@ -3345,13 +3312,11 @@
}
public boolean dispatchContextItemSelected(MenuItem item) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- if (f.performContextItemSelected(item)) {
- return true;
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ if (f.performContextItemSelected(item)) {
+ return true;
}
}
}
@@ -3359,12 +3324,10 @@
}
public void dispatchOptionsMenuClosed(Menu menu) {
- if (mAdded != null) {
- for (int i=0; i<mAdded.size(); i++) {
- Fragment f = mAdded.get(i);
- if (f != null) {
- f.performOptionsMenuClosed(menu);
- }
+ for (int i = 0; i < mAdded.size(); i++) {
+ Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performOptionsMenuClosed(menu);
}
}
}
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index 0dc2cda..f9753eb 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -518,7 +518,7 @@
*/
@RestrictTo(LIBRARY_GROUP)
public float getPixelSize() {
- if ((mVectorState == null && mVectorState.mVPathRenderer == null)
+ if (mVectorState == null || mVectorState.mVPathRenderer == null
|| mVectorState.mVPathRenderer.mBaseWidth == 0
|| mVectorState.mVPathRenderer.mBaseHeight == 0
|| mVectorState.mVPathRenderer.mViewportHeight == 0
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
index 6f5f366..148fef0 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Grid.java
@@ -51,7 +51,17 @@
/**
* Return how many items (are in the adapter).
*/
- public abstract int getCount();
+ int getCount();
+
+ /**
+ * @return Min index to prepend, usually it's 0; but in the preLayout case,
+ * when grid was showing 5,6,7. Removing 3,4,5 will make the layoutPosition to
+ * be 3(deleted),4,5 in prelayout pass; Grid's index is still 5,6,7 in prelayout.
+ * When we prepend in prelayout, we can call createItem(4), createItem(3), createItem(2),
+ * the minimal index is 2, which is also the delta to mapping to layoutPosition in
+ * prelayout pass.
+ */
+ int getMinIndex();
/**
* Create visible item and where the provider should measure it.
@@ -62,7 +72,7 @@
* @param item item[0] returns created item that will be passed in addItem() call.
* @return length of the item.
*/
- public abstract int createItem(int index, boolean append, Object[] item);
+ int createItem(int index, boolean append, Object[] item);
/**
* add item to given row and given edge. The call is always after createItem().
@@ -72,26 +82,26 @@
* @param rowIndex Row index to put the item
* @param edge min_edge if not reversed or max_edge if reversed.
*/
- public abstract void addItem(Object item, int index, int length, int rowIndex, int edge);
+ void addItem(Object item, int index, int length, int rowIndex, int edge);
/**
* Remove visible item at index.
* @param index 0-based index of the item in provider
*/
- public abstract void removeItem(int index);
+ void removeItem(int index);
/**
* Get edge of an existing visible item. edge will be the min_edge
* if not reversed or the max_edge if reversed.
* @param index 0-based index of the item in provider
*/
- public abstract int getEdge(int index);
+ int getEdge(int index);
/**
* Get size of an existing visible item.
* @param index 0-based index of the item in provider
*/
- public abstract int getSize(int index);
+ int getSize(int index);
}
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 8149487..b9d4967 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -17,6 +17,7 @@
import static android.support.v7.widget.RecyclerView.NO_ID;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
import static android.support.v7.widget.RecyclerView.VERTICAL;
import android.content.Context;
@@ -275,13 +276,13 @@
}
void increasePendingMoves() {
- if (mPendingMoves < MAX_PENDING_MOVES) {
+ if (mPendingMoves < mMaxPendingMoves) {
mPendingMoves++;
}
}
void decreasePendingMoves() {
- if (mPendingMoves > -MAX_PENDING_MOVES) {
+ if (mPendingMoves > -mMaxPendingMoves) {
mPendingMoves--;
}
}
@@ -377,7 +378,8 @@
static final boolean TRACE = false;
// maximum pending movement in one direction.
- final static int MAX_PENDING_MOVES = 10;
+ static final int DEFAULT_MAX_PENDING_MOVES = 10;
+ int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
// minimal milliseconds to scroll window size in major direction, we put a cap to prevent the
// effect smooth scrolling too over to bind an item view then drag the item view back.
final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
@@ -423,6 +425,18 @@
private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
RecyclerView.State mState;
+ // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
+ // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
+ // from index of Grid.createItem.
+ int mPositionDeltaInPreLayout;
+ // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
+ // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
+ // the left of focused item might need extra layout on the right.
+ int mExtraLayoutSpaceInPreLayout;
+ // mRetainedFirstPosInPostLayout and mRetainedLastPosInPostLayout are used in post layout pass
+ // to add those item even they are out side visible area.
+ int mRetainedFirstPosInPostLayout;
+ int mRetainedLastPosInPostLayout;
RecyclerView.Recycler mRecycler;
private static final Rect sTempRect = new Rect();
@@ -871,7 +885,7 @@
mChildLaidOutListener = listener;
}
- private int getPositionByView(View view) {
+ private int getAdapterPositionByView(View view) {
if (view == null) {
return NO_POSITION;
}
@@ -908,8 +922,8 @@
return 0;
}
- private int getPositionByIndex(int index) {
- return getPositionByView(getChildAt(index));
+ private int getAdapterPositionByIndex(int index) {
+ return getAdapterPositionByView(getChildAt(index));
}
void dispatchChildSelected() {
@@ -1105,6 +1119,10 @@
}
mRecycler = recycler;
mState = state;
+ mPositionDeltaInPreLayout = 0;
+ mExtraLayoutSpaceInPreLayout = 0;
+ mRetainedFirstPosInPostLayout = Integer.MAX_VALUE;
+ mRetainedLastPosInPostLayout = Integer.MIN_VALUE;
}
/**
@@ -1113,6 +1131,10 @@
private void leaveContext() {
mRecycler = null;
mState = null;
+ mPositionDeltaInPreLayout = 0;
+ mExtraLayoutSpaceInPreLayout = 0;
+ mRetainedFirstPosInPostLayout = Integer.MAX_VALUE;
+ mRetainedLastPosInPostLayout = Integer.MIN_VALUE;
}
/**
@@ -1122,9 +1144,6 @@
* @return true if can fastRelayout()
*/
private boolean layoutInit() {
- boolean focusViewWasInTree = mGrid != null && mFocusPosition >= 0
- && mFocusPosition >= mGrid.getFirstVisibleIndex()
- && mFocusPosition <= mGrid.getLastVisibleIndex();
final int newItemCount = mState.getItemCount();
if (newItemCount == 0) {
mFocusPosition = NO_POSITION;
@@ -1142,13 +1161,9 @@
updateScrollController();
updateSecondaryScrollLimits();
mGrid.setSpacing(mSpacingPrimary);
- if (!focusViewWasInTree && mFocusPosition != NO_POSITION) {
- mGrid.setStart(mFocusPosition);
- }
return true;
} else {
mForceFullLayout = false;
- int firstVisibleIndex = focusViewWasInTree ? mGrid.getFirstVisibleIndex() : 0;
if (mGrid == null || mNumRows != mGrid.getNumRows()
|| mReverseFlowPrimary != mGrid.isReversedFlow()) {
@@ -1163,14 +1178,6 @@
mGrid.resetVisibleIndex();
mWindowAlignment.mainAxis().invalidateScrollMin();
mWindowAlignment.mainAxis().invalidateScrollMax();
- if (focusViewWasInTree && firstVisibleIndex <= mFocusPosition) {
- // if focusView was in tree, we will add item from first visible item
- mGrid.setStart(firstVisibleIndex);
- } else {
- // if focusView was not in tree, it's probably because focus position jumped
- // far away from visible range, so use mFocusPosition as start
- mGrid.setStart(mFocusPosition);
- }
return false;
}
}
@@ -1505,15 +1512,20 @@
private Grid.Provider mGridProvider = new Grid.Provider() {
@Override
+ public int getMinIndex() {
+ return mPositionDeltaInPreLayout;
+ }
+
+ @Override
public int getCount() {
- return mState.getItemCount();
+ return mState.getItemCount() + mPositionDeltaInPreLayout;
}
@Override
public int createItem(int index, boolean append, Object[] item) {
if (TRACE) TraceCompat.beginSection("createItem");
if (TRACE) TraceCompat.beginSection("getview");
- View v = getViewForPosition(index);
+ View v = getViewForPosition(index - mPositionDeltaInPreLayout);
if (TRACE) TraceCompat.endSection();
LayoutParams lp = (LayoutParams) v.getLayoutParams();
RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
@@ -1610,7 +1622,7 @@
@Override
public void removeItem(int index) {
if (TRACE) TraceCompat.beginSection("removeItem");
- View v = findViewByPosition(index);
+ View v = findViewByPosition(index - mPositionDeltaInPreLayout);
if (mInLayout) {
detachAndScrapView(v, mRecycler);
} else {
@@ -1622,15 +1634,15 @@
@Override
public int getEdge(int index) {
if (mReverseFlowPrimary) {
- return getViewMax(findViewByPosition(index));
+ return getViewMax(findViewByPosition(index - mPositionDeltaInPreLayout));
} else {
- return getViewMin(findViewByPosition(index));
+ return getViewMin(findViewByPosition(index - mPositionDeltaInPreLayout));
}
}
@Override
public int getSize(int index) {
- return getViewPrimarySize(findViewByPosition(index));
+ return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
}
};
@@ -1719,14 +1731,16 @@
private void removeInvisibleViewsAtEnd() {
if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtEnd(mFocusPosition,
+ int retainedLastPos = Math.max(mRetainedLastPosInPostLayout, mFocusPosition);
+ mGrid.removeInvisibleItemsAtEnd(retainedLastPos,
mReverseFlowPrimary ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
}
}
private void removeInvisibleViewsAtFront() {
if (mPruneChild && !mIsSlidingChildViews) {
- mGrid.removeInvisibleItemsAtFront(mFocusPosition,
+ int retainedFirstPos = Math.min(mRetainedFirstPosInPostLayout, mFocusPosition);
+ mGrid.removeInvisibleItemsAtFront(retainedFirstPos,
mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace: -mExtraLayoutSpace);
}
}
@@ -1823,13 +1837,15 @@
}
private void appendVisibleItems() {
- mGrid.appendVisibleItems(mReverseFlowPrimary ? -mExtraLayoutSpace
- : mSizePrimary + mExtraLayoutSpace);
+ mGrid.appendVisibleItems(mReverseFlowPrimary
+ ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
+ : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
}
private void prependVisibleItems() {
- mGrid.prependVisibleItems(mReverseFlowPrimary ? mSizePrimary + mExtraLayoutSpace
- : -mExtraLayoutSpace);
+ mGrid.prependVisibleItems(mReverseFlowPrimary
+ ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
+ : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
}
/**
@@ -1843,7 +1859,7 @@
int position = -1;
for (int index = 0; index < childCount; index++) {
View view = getChildAt(index);
- position = getPositionByIndex(index);
+ position = getAdapterPositionByView(view);
Grid.Location location = mGrid.getLocation(position);
if (location == null) {
if (DEBUG) Log.w(getTag(), "fastRelayout(): no Location at " + position);
@@ -1916,7 +1932,7 @@
// called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
// and scroll to the view if framework focus on it.
- private void scrollToFocusViewInLayout(boolean hadFocus, boolean alignToView) {
+ private void focusToViewInLayout(boolean hadFocus, boolean alignToView) {
View focusView = findViewByPosition(mFocusPosition);
if (focusView != null && alignToView) {
scrollToView(focusView, false);
@@ -1958,6 +1974,11 @@
}
}
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return true;
+ }
+
// Lays out items based on the current scroll position
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -1993,14 +2014,66 @@
}
mInLayout = true;
- if (state.didStructureChange()) {
- // didStructureChange() == true means attached item has been removed/added.
- // scroll animation: we are unable to continue a scroll animation,
- // kill the scroll animation, and let ItemAnimation move the item to new position.
- // position smooth scroller: kill the animation and stop at final position.
- // pending smooth scroller: stop and scroll to current focus position.
- mBaseGridView.stopScroll();
+ saveContext(recycler, state);
+ if (state.isPreLayout()) {
+ int childCount = getChildCount();
+ if (mGrid != null && childCount > 0) {
+ int minChangedEdge = Integer.MAX_VALUE;
+ int maxChangeEdge = Integer.MIN_VALUE;
+ for (int i = 0; i < childCount; i++) {
+ View view = getChildAt(i);
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (i == 0) {
+ // first child's layout position can be smaller than index if there were
+ // items removed before first visible index.
+ mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
+ - lp.getViewLayoutPosition();
+ }
+ // if either of following happening
+ // 1. item itself has changed or layout parameter changed
+ // 2. item is losing focus
+ // 3. item is gaining focus
+ if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
+ || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
+ || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())) {
+ minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
+ maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
+ }
+ }
+ if (maxChangeEdge > minChangedEdge) {
+ mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
+ }
+ // append items for mExtraLayoutSpaceInPreLayout
+ appendVisibleItems();
+ prependVisibleItems();
+ }
+ mInLayout = false;
+ leaveContext();
+ if (DEBUG) Log.v(getTag(), "layoutChildren end");
+ return;
}
+
+ // figure out the child positions that needs to be retained in post layout pass, e.g.
+ // When a child is pushed out, they needs to be added in post layout pass.
+ if (state.willRunPredictiveAnimations() && getChildCount() > 0) {
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ int pos = mBaseGridView.getChildViewHolder(child).getAdapterPosition();
+ if (pos >= 0) {
+ mRetainedFirstPosInPostLayout = pos;
+ break;
+ }
+ }
+ for (int i = getChildCount() - 1; i >= 0; i--) {
+ View child = getChildAt(i);
+ int pos = mBaseGridView.getChildViewHolder(child).getAdapterPosition();
+ if (pos >= 0) {
+ mRetainedLastPosInPostLayout = pos;
+ break;
+ }
+ }
+ }
+ // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
final boolean scrollToFocus = !isSmoothScrolling()
&& mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
@@ -2008,37 +2081,35 @@
mSubFocusPosition = 0;
}
mFocusPositionOffset = 0;
- saveContext(recycler, state);
View savedFocusView = findViewByPosition(mFocusPosition);
int savedFocusPos = mFocusPosition;
int savedSubFocusPos = mSubFocusPosition;
boolean hadFocus = mBaseGridView.hasFocus();
-
- // Track the old focus view so we can adjust our system scroll position
- // so that any scroll animations happening now will remain valid.
- // We must use same delta in Pre Layout (if prelayout exists) and second layout.
- // So we cache the deltas in PreLayout and use it in second layout.
- int delta = 0, deltaSecondary = 0;
- if (mFocusPosition != NO_POSITION && scrollToFocus
- && mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
- // FIXME: we should get the remaining scroll animation offset from RecyclerView
- if (savedFocusView != null) {
- if (getScrollPosition(savedFocusView, savedFocusView.findFocus(), sTwoInts)) {
- delta = sTwoInts[0];
- deltaSecondary = sTwoInts[1];
- }
- }
- }
+ final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
+ final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
if (mInFastRelayout = layoutInit()) {
+ // If grid view is empty, we will start from mFocusPosition
+ mGrid.setStart(mFocusPosition);
fastRelayout();
} else {
+ // layoutInit() has detached all views, so start from scratch
mInLayoutSearchFocus = hadFocus;
- if (mFocusPosition != NO_POSITION) {
- // appends items till focus position.
- while (appendOneColumnVisibleItems()
- && findViewByPosition(mFocusPosition) == null) ;
+ int startFromPosition, endPos;
+ if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
+ || mFocusPosition < firstVisibleIndex)) {
+ startFromPosition = endPos = mFocusPosition;
+ } else {
+ startFromPosition = firstVisibleIndex;
+ endPos = lastVisibleIndex;
+ }
+
+ mGrid.setStart(startFromPosition);
+ if (endPos != NO_POSITION) {
+ while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
+ // continuously append items until endPos
+ }
}
}
// multiple rounds: scrollToView of first round may drag first/last child into
@@ -2050,7 +2121,7 @@
updateScrollLimits();
oldFirstVisible = mGrid.getFirstVisibleIndex();
oldLastVisible = mGrid.getLastVisibleIndex();
- scrollToFocusViewInLayout(hadFocus, scrollToFocus);
+ focusToViewInLayout(hadFocus, scrollToFocus);
appendVisibleItems();
prependVisibleItems();
removeInvisibleViewsAtFront();
@@ -2058,14 +2129,32 @@
} while (mGrid.getFirstVisibleIndex() != oldFirstVisible
|| mGrid.getLastVisibleIndex() != oldLastVisible);
- if (scrollToFocus) {
- scrollDirectionPrimary(-delta);
- scrollDirectionSecondary(-deltaSecondary);
+ if (scrollToFocus && mBaseGridView.getScrollState() == SCROLL_STATE_SETTLING) {
+ // if still running scroll animation (excluding smoothscroll case), compensate for
+ // remaining distance so that the animation will continue and later stopped at
+ // perfectly aligned position.
+ final int remainingScrollX = state.getRemainingScrollHorizontal();
+ final int remainingScrollY = state.getRemainingScrollVertical();
+ if (remainingScrollX != 0 || remainingScrollY != 0) {
+ if (mOrientation == HORIZONTAL) {
+ scrollDirectionPrimary(-remainingScrollX);
+ scrollDirectionSecondary(-remainingScrollY);
+ } else {
+ scrollDirectionPrimary(-remainingScrollY);
+ scrollDirectionSecondary(-remainingScrollX);
+ }
+ appendVisibleItems();
+ prependVisibleItems();
+ removeInvisibleViewsAtFront();
+ removeInvisibleViewsAtEnd();
+ }
}
- appendVisibleItems();
- prependVisibleItems();
- removeInvisibleViewsAtFront();
- removeInvisibleViewsAtEnd();
+ while (mGrid.getLastVisibleIndex() < mRetainedLastPosInPostLayout) {
+ appendOneColumnVisibleItems();
+ }
+ while (mGrid.getFirstVisibleIndex() > mRetainedFirstPosInPostLayout) {
+ prependOneColumnVisibleItems();
+ }
if (DEBUG) {
StringWriter sw = new StringWriter();
@@ -2090,10 +2179,10 @@
dispatchChildSelected();
}
dispatchChildSelectedAndPositioned();
-
if (mIsSlidingChildViews) {
scrollDirectionPrimary(getSlideOutDistance());
}
+
mInLayout = false;
leaveContext();
if (DEBUG) Log.v(getTag(), "layoutChildren end");
@@ -2524,8 +2613,8 @@
int pos = mFocusPosition + mFocusPositionOffset;
if (positionStart <= pos) {
if (positionStart + itemCount > pos) {
- // stop updating offset after the focus item was removed
- mFocusPositionOffset = Integer.MIN_VALUE;
+ // the focus item was removed
+ mFocusPositionOffset += positionStart - pos;
} else {
mFocusPositionOffset -= itemCount;
}
@@ -2569,7 +2658,7 @@
if (mFocusSearchDisabled) {
return true;
}
- if (getPositionByView(child) == NO_POSITION) {
+ if (getAdapterPositionByView(child) == NO_POSITION) {
// This is could be the last view in DISAPPEARING animation.
return true;
}
@@ -2637,7 +2726,7 @@
if (mIsSlidingChildViews) {
return;
}
- int newFocusPosition = getPositionByView(view);
+ int newFocusPosition = getAdapterPositionByView(view);
int newSubFocusPosition = getSubPositionByView(view, childView);
if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
mFocusPosition = newFocusPosition;
@@ -2678,7 +2767,7 @@
}
private boolean getNoneAlignedPosition(View view, int[] deltas) {
- int pos = getPositionByView(view);
+ int pos = getAdapterPositionByView(view);
int viewMin = getViewMin(view);
int viewMax = getViewMax(view);
// we either align "firstView" to left/top padding edge
@@ -2942,7 +3031,7 @@
}
final int focusedRow = mGrid.getLocation(pos).row;
for (int i = getChildCount() - 1; i >= 0; i--) {
- int position = getPositionByIndex(i);
+ int position = getAdapterPositionByIndex(i);
Grid.Location loc = mGrid.getLocation(position);
if (loc != null && loc.row == focusedRow) {
if (position < pos) {
@@ -2974,7 +3063,7 @@
final int movement = getMovement(direction);
final View focused = recyclerView.findFocus();
final int focusedIndex = findImmediateChildIndex(focused);
- final int focusedPos = getPositionByIndex(focusedIndex);
+ final int focusedPos = getAdapterPositionByIndex(focusedIndex);
// Add focusables of focused item.
if (focusedPos != NO_POSITION) {
findViewByPosition(focusedPos).addFocusables(views, direction, focusableMode);
@@ -3013,7 +3102,7 @@
}
continue;
}
- int position = getPositionByIndex(i);
+ int position = getAdapterPositionByIndex(i);
Grid.Location loc = mGrid.getLocation(position);
if (loc == null) {
continue;
@@ -3315,7 +3404,7 @@
// save views currently is on screen (TODO save cached views)
for (int i = 0, count = getChildCount(); i < count; i++) {
View view = getChildAt(i);
- int position = getPositionByView(view);
+ int position = getAdapterPositionByView(view);
if (position != NO_POSITION) {
bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
}
@@ -3426,7 +3515,7 @@
if (!canScrollTo(child)) {
continue;
}
- int position = getPositionByIndex(index);
+ int position = getAdapterPositionByIndex(index);
int rowIndex = mGrid.getRowIndex(position);
if (focusedRow == NO_POSITION) {
focusPosition = position;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java b/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
index 04a384a..226bd51 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SingleRow.java
@@ -76,7 +76,8 @@
return false;
}
boolean filledOne = false;
- for (int index = getStartIndexForPrepend(); index >= 0; index--) {
+ int minIndex = mProvider.getMinIndex();
+ for (int index = getStartIndexForPrepend(); index >= minIndex; index--) {
int size = mProvider.createItem(index, false, mTmpItem);
int edge;
if (mFirstVisibleIndex < 0 || mLastVisibleIndex < 0) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
index 7fa54e2..eb9528e 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/StaggeredGrid.java
@@ -141,8 +141,6 @@
if (mLocations.size() == 0) {
return false;
}
- final int count = mProvider.getCount();
- final int firstIndex = getFirstIndex();
int itemIndex;
int edge;
int offset;
@@ -165,7 +163,8 @@
return false;
}
}
- for (; itemIndex >= mFirstIndex; itemIndex--) {
+ int firstIndex = Math.max(mProvider.getMinIndex(), mFirstIndex);
+ for (; itemIndex >= firstIndex; itemIndex--) {
Location loc = getLocation(itemIndex);
int rowIndex = loc.row;
int size = mProvider.createItem(itemIndex, false, mTmpItem);
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
index 2daba66..519f1de 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/ProgressBarManagerTest.java
@@ -19,7 +19,7 @@
import android.content.Context;
import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
+import android.support.test.filters.LargeTest;
import android.support.v17.leanback.testutils.PollingCheck;
import android.view.View;
import android.view.ViewGroup;
@@ -28,7 +28,7 @@
import org.junit.Before;
import org.junit.Test;
-@SmallTest
+@LargeTest
public class ProgressBarManagerTest {
Context mContext;
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
index 6f5529e..605adfa 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridTest.java
@@ -33,6 +33,11 @@
}
@Override
+ public int getMinIndex() {
+ return 0;
+ }
+
+ @Override
public int getCount() {
return mCount;
}
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 4d4bede..8b38db0 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -442,16 +442,19 @@
* To wait the ItemAnimator start, you can use waitForLayout() to make sure layout pass has
* processed adapter change.
*/
- protected void waitForItemAnimation(int timeoutMs) {
- RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
+ protected void waitForItemAnimation(int timeoutMs) throws Throwable {
+ final RecyclerView.ItemAnimator.ItemAnimatorFinishedListener listener = mock(
RecyclerView.ItemAnimator.ItemAnimatorFinishedListener.class);
- if (mGridView.getItemAnimator().isRunning(listener)) {
- verify(listener, timeout(timeoutMs).atLeastOnce())
- .onAnimationsFinished();
- }
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.getItemAnimator().isRunning(listener);
+ }
+ });
+ verify(listener, timeout(timeoutMs).atLeastOnce()).onAnimationsFinished();
}
- protected void waitForItemAnimation() {
+ protected void waitForItemAnimation() throws Throwable {
waitForItemAnimation(WAIT_FOR_ITEM_ANIMATION_FINISH_TIMEOUT_MS);
}
@@ -774,6 +777,241 @@
verifyEdgesSame(endEdges, endEdges2);
}
+ void preparePredictiveLayout() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.getItemAnimator().setAddDuration(1000);
+ mGridView.getItemAnimator().setRemoveDuration(1000);
+ mGridView.getItemAnimator().setMoveDuration(1000);
+ mGridView.getItemAnimator().setChangeDuration(1000);
+ mGridView.setSelectedPositionSmooth(50);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+ }
+
+ @Test
+ public void testPredictiveLayoutAdd1() throws Throwable {
+ preparePredictiveLayout();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.addItems(51, new int[]{300, 300, 300, 300});
+ }
+ });
+ waitForItemAnimationStart();
+ waitForItemAnimation(5000);
+ assertEquals(50, mGridView.getSelectedPosition());
+ assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+ }
+
+ @Test
+ public void testPredictiveLayoutAdd2() throws Throwable {
+ preparePredictiveLayout();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.addItems(50, new int[]{300, 300, 300, 300});
+ }
+ });
+ waitForItemAnimationStart();
+ waitForItemAnimation(5000);
+ assertEquals(54, mGridView.getSelectedPosition());
+ assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+ }
+
+ @Test
+ public void testPredictiveLayoutRemove1() throws Throwable {
+ preparePredictiveLayout();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(51, 3);
+ }
+ });
+ waitForItemAnimationStart();
+ waitForItemAnimation(5000);
+ assertEquals(50, mGridView.getSelectedPosition());
+ assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+ }
+
+ @Test
+ public void testPredictiveLayoutRemove2() throws Throwable {
+ preparePredictiveLayout();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(46, 3);
+ }
+ });
+ waitForItemAnimationStart();
+ waitForItemAnimation(5000);
+ assertEquals(47, mGridView.getSelectedPosition());
+ assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+ }
+
+ @Test
+ public void testPredictiveLayoutRemove3() throws Throwable {
+ preparePredictiveLayout();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(0, 51);
+ }
+ });
+ waitForItemAnimationStart();
+ waitForItemAnimation(5000);
+ assertEquals(0, mGridView.getSelectedPosition());
+ assertEquals(RecyclerView.SCROLL_STATE_IDLE, mGridView.getScrollState());
+ }
+
+ @Test
+ public void testPredictiveLayoutRemove4() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_grid);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 3;
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.setSelectedPositionSmooth(50);
+ }
+ });
+ waitForScrollIdle();
+ performAndWaitForAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(0, 49);
+ }
+ });
+ assertEquals(1, mGridView.getSelectedPosition());
+ }
+
+ @Test
+ public void testPredictiveLayoutRemove5() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_grid);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, true);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 3;
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.setSelectedPositionSmooth(50);
+ }
+ });
+ waitForScrollIdle();
+ performAndWaitForAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(50, 40);
+ }
+ });
+ assertEquals(50, mGridView.getSelectedPosition());
+ scrollToBegin(mVerifyLayout);
+ verifyBeginAligned();
+ }
+
+ @Test
+ public void testContinuousSwapForward() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.setSelectedPositionSmooth(150);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+ for (int i = 150; i < 199; i++) {
+ final int swapIndex = i;
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.swap(swapIndex, swapIndex + 1);
+ }
+ });
+ Thread.sleep(10);
+ }
+ waitForItemAnimation();
+ assertEquals(199, mGridView.getSelectedPosition());
+ // check if ItemAnimation finishes at aligned positions:
+ int leftEdge = mGridView.getLayoutManager().findViewByPosition(199).getLeft();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.requestLayout();
+ }
+ });
+ waitForScrollIdle();
+ assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(199).getLeft());
+ }
+
+ @Test
+ public void testContinuousSwapBackward() throws Throwable {
+ Intent intent = new Intent();
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 200);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.setSelectedPositionSmooth(50);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+ for (int i = 50; i > 0; i--) {
+ final int swapIndex = i;
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.swap(swapIndex, swapIndex - 1);
+ }
+ });
+ Thread.sleep(10);
+ }
+ waitForItemAnimation();
+ assertEquals(0, mGridView.getSelectedPosition());
+ // check if ItemAnimation finishes at aligned positions:
+ int leftEdge = mGridView.getLayoutManager().findViewByPosition(0).getLeft();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.requestLayout();
+ }
+ });
+ waitForScrollIdle();
+ assertEquals(leftEdge, mGridView.getLayoutManager().findViewByPosition(0).getLeft());
+ }
+
@Test
public void testItemMovedHorizontal() throws Throwable {
Intent intent = new Intent();
@@ -784,7 +1022,12 @@
mOrientation = BaseGridView.HORIZONTAL;
mNumRows = 3;
- mGridView.setSelectedPositionSmooth(150);
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.setSelectedPositionSmooth(150);
+ }
+ });
waitForScrollIdle(mVerifyLayout);
performAndWaitForAnimation(new Runnable() {
@Override
@@ -1216,13 +1459,24 @@
}
});
- performAndWaitForAnimation(new Runnable() {
+ mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
int[] newItems = new int[]{300, 300, 300};
mActivity.addItems(0, newItems);
}
});
+ waitForScrollIdle();
+ int topEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop();
+ mActivityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.requestLayout();
+ }
+ });
+ waitForScrollIdle();
+ assertEquals(topEdge,
+ mGridView.getLayoutManager().findViewByPosition(focusToIndex).getTop());
}
@Test
@@ -1274,12 +1528,12 @@
Intent intent = new Intent();
intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.horizontal_linear);
- intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
initActivity(intent);
mOrientation = BaseGridView.HORIZONTAL;
mNumRows = 1;
- final int focusToIndex = 40;
+ final int focusToIndex = 200;
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1316,12 +1570,12 @@
Intent intent = new Intent();
intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
R.layout.horizontal_linear);
- intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 300);
initActivity(intent);
mOrientation = BaseGridView.HORIZONTAL;
mNumRows = 1;
- final int focusToIndex = 40;
+ final int focusToIndex = 200;
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -1340,9 +1594,10 @@
});
waitForLayout();
- assertFalse("removing the index of attached child should kill smooth scroller",
+ assertTrue("removing the index of attached child should not kill smooth scroller",
mGridView.getLayoutManager().isSmoothScrolling());
waitForItemAnimation();
+ waitForScrollIdle();
int leftEdge = mGridView.getLayoutManager().findViewByPosition(focusToIndex).getLeft();
mActivityTestRule.runOnUiThread(new Runnable() {
@@ -1378,7 +1633,8 @@
assertTrue(mGridView.getChildAt(0).hasFocus());
// Pressing lots of key to make sure smooth scroller is running
- for (int i = 0; i < 20; i++) {
+ mGridView.mLayoutManager.mMaxPendingMoves = 100;
+ for (int i = 0; i < 100; i++) {
sendKey(KeyEvent.KEYCODE_DPAD_DOWN);
}
@@ -1394,10 +1650,11 @@
});
waitForLayout();
- assertFalse("removing the index of attached child should kill smooth scroller",
+ assertTrue("removing the index of attached child should not kill smooth scroller",
mGridView.getLayoutManager().isSmoothScrolling());
waitForItemAnimation();
+ waitForScrollIdle();
int focusIndex = mGridView.getSelectedPosition();
int topEdge = mGridView.getLayoutManager().findViewByPosition(focusIndex).getTop();
@@ -2349,7 +2606,8 @@
mGridView.stopScroll();
}
});
- Thread.sleep(100);
+ waitForScrollIdle();
+ waitForItemAnimation();
assertEquals(mGridView.getSelectedPosition(), targetPosition);
assertSame(mGridView.getLayoutManager().findViewByPosition(targetPosition),
mGridView.findFocus());
@@ -3797,7 +4055,7 @@
initActivity(intent);
- final HashSet<View> removeAnimationFinishedViews = new HashSet();
+ final HashSet<View> moveAnimationViews = new HashSet();
mActivity.mImportantForAccessibilityListener =
new GridActivity.ImportantForAccessibilityListener() {
RecyclerView.LayoutManager mLM = mGridView.getLayoutManager();
@@ -3805,7 +4063,7 @@
public void onImportantForAccessibilityChanged(View view, int newValue) {
// simulates talkack, having setImportantForAccessibility to call
// onInitializeAccessibilityNodeInfoForItem() for the DISAPPEARING items.
- if (removeAnimationFinishedViews.contains(view)) {
+ if (moveAnimationViews.contains(view)) {
AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
mLM.onInitializeAccessibilityNodeInfoForItem(
null, null, view, info);
@@ -3814,19 +4072,23 @@
};
final int lastPos = mGridView.getChildAdapterPosition(
mGridView.getChildAt(mGridView.getChildCount() - 1));
+ final int numItemsToPushOut = mNumRows;
+ for (int i = 0; i < numItemsToPushOut; i++) {
+ moveAnimationViews.add(
+ mGridView.getChildAt(mGridView.getChildCount() - 1 - i));
+ }
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
mGridView.setItemAnimator(new DefaultItemAnimator() {
@Override
- public void onRemoveFinished(RecyclerView.ViewHolder item) {
- removeAnimationFinishedViews.add(item.itemView);
+ public void onMoveFinished(RecyclerView.ViewHolder item) {
+ moveAnimationViews.remove(item.itemView);
}
});
mGridView.getLayoutManager().setItemPrefetchEnabled(false);
}
});
- final int numItemsToPushOut = mNumRows;
mActivityTestRule.runOnUiThread(new Runnable() {
@Override
public void run() {
@@ -3838,7 +4100,7 @@
mActivity.addItems(lastPos - numItemsToPushOut + 1, newItems);
}
});
- while (removeAnimationFinishedViews.size() != numItemsToPushOut) {
+ while (moveAnimationViews.size() != 0) {
Thread.sleep(100);
}
}
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java b/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
deleted file mode 100644
index 1834681..0000000
--- a/v7/appcompat/src/android/support/v7/app/ActionBarActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-package android.support.v7.app;
-
-/**
- * @deprecated Use {@link android.support.v7.app.AppCompatActivity} instead.
- */
-@Deprecated
-public class ActionBarActivity extends AppCompatActivity {
-}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index 5b9f337..afc951a 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -54,6 +54,9 @@
private final @NonNull AppCompatTextViewAutoSizeHelper mAutoSizeTextHelper;
+ private int mStyle = Typeface.NORMAL;
+ private Typeface mFontTypeface;
+
AppCompatTextHelper(TextView view) {
mView = view;
mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView);
@@ -62,7 +65,6 @@
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
final Context context = mView.getContext();
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
- final boolean shouldLoadFonts = shouldLoadFontResources(context);
// First read the TextAppearance style id
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
@@ -97,8 +99,6 @@
ColorStateList textColor = null;
ColorStateList textColorHint = null;
ColorStateList textColorLink = null;
- Typeface fontTypeface = null;
- int style = Typeface.NORMAL;
// First check TextAppearance's textAllCaps value
if (ap != -1) {
@@ -107,27 +107,8 @@
allCapsSet = true;
allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
}
- if (shouldLoadFonts) {
- style = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
- // If we're running on < API 26, we need to load font resources manually.
- if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
- int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- ? R.styleable.TextAppearance_android_fontFamily
- : R.styleable.TextAppearance_fontFamily;
- try {
- fontTypeface = a.getFont(fontFamilyId, style);
- } catch (UnsupportedOperationException | Resources.NotFoundException e) {
- // Expected if it is not a font resource.
- }
- if (fontTypeface == null) {
- // Try with String. This is done by TextView JB+, but fails in ICS
- String fontFamilyName = a.getString(fontFamilyId);
- fontTypeface = Typeface.create(fontFamilyName, style);
- }
- }
- }
+ updateTypefaceAndStyle(context, a);
if (Build.VERSION.SDK_INT < 23) {
// If we're running on < API 23, the text color may contain theme references
// so let's re-set using our own inflater
@@ -169,26 +150,7 @@
}
}
- if (shouldLoadFonts) {
- // If we're running on < API 26, we need to load font resources manually.
- if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
- int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
- ? R.styleable.TextAppearance_android_fontFamily
- : R.styleable.TextAppearance_fontFamily;
- style = a.getInt(R.styleable.TextAppearance_android_textStyle, Typeface.NORMAL);
- try {
- fontTypeface = a.getFont(fontFamilyId, style);
- } catch (UnsupportedOperationException | Resources.NotFoundException e) {
- // Expected if it is not a font resource.
- }
- if (fontTypeface == null) {
- // Try with String. This is done by TextView JB+, but fails in ICS
- String fontFamilyName = a.getString(fontFamilyId);
- fontTypeface = Typeface.create(fontFamilyName, style);
- }
- }
- }
+ updateTypefaceAndStyle(context, a);
a.recycle();
if (textColor != null) {
@@ -203,8 +165,8 @@
if (!hasPwdTm && allCapsSet) {
setAllCaps(allCaps);
}
- if (fontTypeface != null) {
- mView.setTypeface(fontTypeface, style);
+ if (mFontTypeface != null) {
+ mView.setTypeface(mFontTypeface, mStyle);
}
mAutoSizeTextHelper.loadFromAttributes(attrs, defStyleAttr);
@@ -233,9 +195,27 @@
}
}
- private boolean shouldLoadFontResources(Context context) {
- // We do not load fonts on restricted contexts for security reasons.
- return !context.isRestricted();
+ private void updateTypefaceAndStyle(Context context, TintTypedArray a) {
+ mStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, mStyle);
+
+ if (a.hasValue(R.styleable.TextAppearance_android_fontFamily)
+ || a.hasValue(R.styleable.TextAppearance_fontFamily)) {
+ int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily)
+ ? R.styleable.TextAppearance_android_fontFamily
+ : R.styleable.TextAppearance_fontFamily;
+ if (!context.isRestricted()) {
+ try {
+ mFontTypeface = a.getFont(fontFamilyId, mStyle);
+ } catch (UnsupportedOperationException | Resources.NotFoundException e) {
+ // Expected if it is not a font resource.
+ }
+ }
+ if (mFontTypeface == null) {
+ // Try with String. This is done by TextView JB+, but fails in ICS
+ String fontFamilyName = a.getString(fontFamilyId);
+ mFontTypeface = Typeface.create(fontFamilyName, mStyle);
+ }
+ }
}
void onSetTextAppearance(Context context, int resId) {
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
index f857a85..056f2dc 100644
--- a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -146,6 +146,22 @@
android:layout_height="wrap_content"
app:fontFamily="@font/samplexmlfont" />
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/textview_fontxmlresource_fontfamily_textstyle"
+ android:text="@string/sample_text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textStyle="italic"
+ app:fontFamily="@font/samplexmlfont" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/textview_fontxmlresource_fontfamily_textstyle2"
+ android:text="@string/sample_text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textStyle="italic"
+ android:fontFamily="@font/samplexmlfont" />
+
<!-- This is here to test that the TextView constructor ignores references to
non Font resource types in the fontFamily attribute.-->
<android.support.v7.widget.AppCompatTextView
@@ -183,6 +199,20 @@
android:layout_height="wrap_content"
android:textAppearance="@style/TextView_FontXmlResource" />
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/textview_textStyleOverride"
+ android:text="@string/sample_text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextView_TextStyleOverride" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/textview_textStyleDirect"
+ android:text="@string/sample_text1"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textStyle="italic" />
+
</LinearLayout>
</ScrollView>
diff --git a/v7/appcompat/tests/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
index e6e593b..05e2fa5 100644
--- a/v7/appcompat/tests/res/values/styles.xml
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -61,4 +61,8 @@
<style name="TextView_FontXmlResource">
<item name="fontFamily">@font/samplexmlfont</item>
</style>
+
+ <style name="TextView_TextStyleOverride">
+ <item name="android:textStyle">italic</item>
+ </style>
</resources>
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
index b7a28af..a9da3f5 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
@@ -28,6 +28,7 @@
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.IdRes;
+import android.support.test.filters.LargeTest;
import android.support.test.filters.SmallTest;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.res.ResourcesCompat;
@@ -88,11 +89,13 @@
onView(withText(itemText)).perform(click());
}
+ @LargeTest
@Test
public void testPopupThemingFromXmlAttribute() {
verifySpinnerPopupTheming(R.id.view_magenta_themed_popup, R.color.test_magenta, true);
}
+ @LargeTest
@Test
public void testUnthemedPopupRuntimeTheming() {
final AppCompatSpinner spinner =
@@ -106,6 +109,7 @@
verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_green, false);
}
+ @LargeTest
@Test
public void testThemedPopupRuntimeTheming() {
final AppCompatSpinner spinner =
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
index 0d357b0..cddf5e5 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -21,6 +21,7 @@
import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import android.content.res.ColorStateList;
@@ -170,6 +171,22 @@
}
@Test
+ public void testFontResourcesXml_setInXmlFamilyNameWithTextStyle() {
+ TextView textView =
+ mContainer.findViewById(R.id.textview_fontxmlresource_fontfamily_textstyle);
+
+ assertNotEquals(Typeface.DEFAULT, textView.getTypeface());
+ }
+
+ @Test
+ public void testFontResourcesXml_setInXmlFamilyNameWithTextStyle2() {
+ TextView textView =
+ mContainer.findViewById(R.id.textview_fontxmlresource_fontfamily_textstyle2);
+
+ assertNotEquals(Typeface.DEFAULT, textView.getTypeface());
+ }
+
+ @Test
public void testFontResources_setInXmlStyle() {
TextView textView = mContainer.findViewById(R.id.textview_fontresource_style);
Typeface expected = ResourcesCompat.getFont(mActivity, R.font.samplefont);
@@ -200,4 +217,19 @@
assertEquals(expected, textView.getTypeface());
}
+
+ @Test
+ public void testTextStyle_setTextStyleInStyle() {
+ // TextView has a TextAppearance by default, but the textStyle can be overriden in style.
+ TextView textView = mContainer.findViewById(R.id.textview_textStyleOverride);
+
+ assertEquals(Typeface.ITALIC, textView.getTypeface().getStyle());
+ }
+
+ @Test
+ public void testTextStyle_setTextStyleDirectly() {
+ TextView textView = mContainer.findViewById(R.id.textview_textStyleDirect);
+
+ assertEquals(Typeface.ITALIC, textView.getTypeface().getStyle());
+ }
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 3d33d24..a7bceec 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -20,6 +20,7 @@
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY_PAUSE;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_STOP;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import android.app.PendingIntent;
import android.content.ContentResolver;
@@ -72,6 +73,7 @@
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
+
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -1438,7 +1440,8 @@
@Override
protected void onPostExecute(Bitmap art) {
mFetchArtTask = null;
- if (mArtIconBitmap != mIconBitmap || mArtIconUri != mIconUri) {
+ if (!objectEquals(mArtIconBitmap, mIconBitmap)
+ || !objectEquals(mArtIconUri, mIconUri)) {
mArtIconBitmap = mIconBitmap;
mArtIconLoadedBitmap = art;
mArtIconUri = mIconUri;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
index eee05cc..5467c40 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProvider.java
@@ -17,6 +17,7 @@
package android.support.v7.media;
import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import android.content.ComponentName;
import android.content.Context;
@@ -150,8 +151,7 @@
public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
MediaRouter.checkCallingThread();
- if (mDiscoveryRequest == request
- || (mDiscoveryRequest != null && mDiscoveryRequest.equals(request))) {
+ if (objectEquals(mDiscoveryRequest, request)) {
return;
}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index 9ddde89..0d9c396 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -16,6 +16,7 @@
package android.support.v7.media;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_ID;
import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_ROUTE_LIBRARY_GROUP;
import static android.support.v7.media.MediaRouteProviderProtocol.CLIENT_DATA_UNSELECT_REASON;
@@ -463,9 +464,7 @@
if (selectorBuilder != null) {
composite = new MediaRouteDiscoveryRequest(selectorBuilder.build(), activeScan);
}
- if (mCompositeDiscoveryRequest != composite
- && (mCompositeDiscoveryRequest == null
- || !mCompositeDiscoveryRequest.equals(composite))) {
+ if (!objectEquals(mCompositeDiscoveryRequest, composite)) {
mCompositeDiscoveryRequest = composite;
mProvider.setDiscoveryRequest(composite);
return true;
@@ -615,8 +614,7 @@
}
public boolean setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
- if (mDiscoveryRequest != request
- && (mDiscoveryRequest == null || !mDiscoveryRequest.equals(request))) {
+ if (!objectEquals(mDiscoveryRequest, request)) {
mDiscoveryRequest = request;
return updateCompositeDiscoveryRequest();
}
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index 8e09889..045b69b 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -15,6 +15,8 @@
*/
package android.support.v7.media;
+import static android.support.v4.utils.ObjectUtils.objectEquals;
+
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -202,8 +204,7 @@
* @param sessionId The new session id, or null if none.
*/
public void setSessionId(String sessionId) {
- if (mSessionId != sessionId
- && (mSessionId == null || !mSessionId.equals(sessionId))) {
+ if (!objectEquals(mSessionId, sessionId)) {
if (DEBUG) {
Log.d(TAG, "Session id is now: " + sessionId);
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index cb57d08..8d2d7bd 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -1738,6 +1738,7 @@
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
+ fillRemainingScrollValues(mState);
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
@@ -3548,6 +3549,17 @@
return lastKnownId;
}
+ final void fillRemainingScrollValues(State state) {
+ if (getScrollState() == SCROLL_STATE_SETTLING) {
+ final OverScroller scroller = mViewFlinger.mScroller;
+ state.mRemainingScrollHorizontal = scroller.getFinalX() - scroller.getCurrX();
+ state.mRemainingScrollVertical = scroller.getFinalY() - scroller.getCurrY();
+ } else {
+ state.mRemainingScrollHorizontal = 0;
+ state.mRemainingScrollVertical = 0;
+ }
+ }
+
/**
* The first step of a layout where we;
* - process adapter updates
@@ -3557,6 +3569,7 @@
*/
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
+ fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
eatRequestLayout();
mViewInfoStore.clear();
@@ -4793,6 +4806,7 @@
eatRequestLayout();
onEnterLayoutOrScroll();
TraceCompat.beginSection(TRACE_SCROLL_TAG);
+ fillRemainingScrollValues(mState);
if (dx != 0) {
hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
overscrollX = dx - hresult;
@@ -11584,6 +11598,7 @@
}
};
}
+
/**
* <p>Contains useful information about the current RecyclerView state like target scroll
* position or view focus. State object can also keep arbitrary data, identified by resource
@@ -11673,6 +11688,9 @@
// that one instead
int mFocusedSubChildId;
+ int mRemainingScrollHorizontal;
+ int mRemainingScrollVertical;
+
////////////////////////////////////////////////////////////////////////////////////////////
State reset() {
@@ -11850,6 +11868,28 @@
: mItemCount;
}
+ /**
+ * Returns remaining horizontal scroll distance of an ongoing scroll animation(fling/
+ * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is
+ * other than {@link #SCROLL_STATE_SETTLING}.
+ *
+ * @return Remaining horizontal scroll distance
+ */
+ public int getRemainingScrollHorizontal() {
+ return mRemainingScrollHorizontal;
+ }
+
+ /**
+ * Returns remaining vertical scroll distance of an ongoing scroll animation(fling/
+ * smoothScrollTo/SmoothScroller) in pixels. Returns zero if {@link #getScrollState()} is
+ * other than {@link #SCROLL_STATE_SETTLING}.
+ *
+ * @return Remaining vertical scroll distance
+ */
+ public int getRemainingScrollVertical() {
+ return mRemainingScrollVertical;
+ }
+
@Override
public String toString() {
return "State{"
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
index 1f7beac..5529ead 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java
@@ -201,7 +201,12 @@
recyclerView.setItemViewCacheSize(0); // no cache, directly goes to pool
recyclerView.setLayoutManager(layoutManager);
setRecyclerView(recyclerView);
- recyclerView.setAdapter(adapter);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView.setAdapter(adapter);
+ }
+ });
layoutManager.waitForLayout(1);
assertEquals(layoutCount, recyclerView.getChildCount());
@@ -276,7 +281,12 @@
recyclerView.setItemViewCacheSize(0); // no cache, directly goes to pool
recyclerView.setLayoutManager(layoutManager);
setRecyclerView(recyclerView);
- recyclerView.setAdapter(adapter);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView.setAdapter(adapter);
+ }
+ });
layoutManager.waitForLayout(1);
assertEquals(firstPassLayoutCount, recyclerView.getChildCount());
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index 38b5345..be687c3 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -4571,4 +4571,74 @@
}
}
+ @Test
+ public void testRemainingScrollInLayout() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestAdapter adapter = new TestAdapter(100);
+
+ final CountDownLatch firstScrollDone = new CountDownLatch(1);
+ final CountDownLatch scrollFinished = new CountDownLatch(1);
+ final int[] totalScrollDistance = new int[] {0};
+ recyclerView.setLayoutManager(new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (firstScrollDone.getCount() < 1 && scrollFinished.getCount() == 1) {
+ try {
+ assertTrue("layout pass has remaining scroll",
+ state.getRemainingScrollVertical() != 0);
+ assertEquals("layout pass has remaining scroll",
+ 1000 - totalScrollDistance[0], state.getRemainingScrollVertical());
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ super.onLayoutChildren(recycler, state);
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ firstScrollDone.countDown();
+ totalScrollDistance[0] += dy;
+ if (state.getRemainingScrollVertical() == 0) {
+ // the last scroll pass will have remaining 0
+ scrollFinished.countDown();
+ }
+ return super.scrollVerticallyBy(dy, recycler, state);
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+ });
+ recyclerView.setAdapter(adapter);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ setRecyclerView(recyclerView);
+ recyclerView.smoothScrollBy(0, 1000);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ });
+
+ firstScrollDone.await(1, TimeUnit.SECONDS);
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ recyclerView.requestLayout();
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ });
+ waitForIdleScroll(recyclerView);
+ assertTrue(scrollFinished.getCount() < 1);
+ assertEquals(totalScrollDistance[0], 1000);
+ }
+
}