summaryrefslogtreecommitdiff
path: root/android/support
diff options
context:
space:
mode:
authorJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
committerJustin Klaassen <justinklaassen@google.com>2017-11-30 18:18:21 -0500
commit4217cf85c20565a3446a662a7f07f26137b26b7f (patch)
treea0417b47a8cc802f6642f369fd2371165bec7b5c /android/support
parent6a65f2da209bff03cb0eb6da309710ac6ee5026d (diff)
downloadandroid-28-4217cf85c20565a3446a662a7f07f26137b26b7f.tar.gz
Import Android SDK Platform P [4477446]
/google/data/ro/projects/android/fetch_artifact \ --bid 4477446 \ --target sdk_phone_armv7-win_sdk \ sdk-repo-linux-sources-4477446.zip AndroidVersion.ApiLevel has been modified to appear as 28 Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
Diffstat (limited to 'android/support')
-rw-r--r--android/support/LibraryGroups.java1
-rw-r--r--android/support/car/widget/PagedListView.java32
-rw-r--r--android/support/design/widget/BottomSheetBehavior.java2
-rw-r--r--android/support/design/widget/CollapsingToolbarLayout.java1
-rw-r--r--android/support/design/widget/CoordinatorLayout.java2
-rw-r--r--android/support/design/widget/DirectedAcyclicGraphTest.java207
-rw-r--r--android/support/design/widget/FloatingActionButton.java38
-rw-r--r--android/support/design/widget/TextInputEditText.java7
-rw-r--r--android/support/design/widget/TextInputLayout.java5
-rw-r--r--android/support/doclava/DoclavaJavadocOptionFileOption.java75
-rw-r--r--android/support/graphics/drawable/VectorDrawableCompat.java4
-rw-r--r--android/support/mediacompat/testlib/MediaBrowserConstants.java10
-rw-r--r--android/support/mediacompat/testlib/MediaControllerConstants.java2
-rw-r--r--android/support/mediacompat/testlib/MediaSessionConstants.java2
-rw-r--r--android/support/mediacompat/testlib/VersionConstants.java3
-rw-r--r--android/support/mediacompat/testlib/util/IntentUtil.java9
-rw-r--r--android/support/text/emoji/EmojiCompat.java3
-rw-r--r--android/support/text/emoji/MetadataListReader.java2
-rw-r--r--android/support/text/emoji/widget/EmojiEditableFactory.java1
-rw-r--r--android/support/v17/leanback/widget/GridLayoutManager.java51
-rw-r--r--android/support/v4/graphics/TypefaceCompatApi26Impl.java5
-rw-r--r--android/support/v4/media/MediaBrowserCompat.java12
-rw-r--r--android/support/v4/media/MediaBrowserProtocol.java10
-rw-r--r--android/support/v4/media/MediaBrowserServiceCompat.java73
-rw-r--r--android/support/v4/view/NestedScrollingParent2.java3
-rw-r--r--android/support/v4/view/PagerAdapter.java1
-rw-r--r--android/support/v4/view/ViewPager.java4
-rw-r--r--android/support/v4/widget/DirectedAcyclicGraph.java (renamed from android/support/design/widget/DirectedAcyclicGraph.java)39
-rw-r--r--android/support/v4/widget/ViewGroupUtils.java (renamed from android/support/design/widget/ViewGroupUtils.java)15
-rw-r--r--android/support/v7/preference/CollapsiblePreferenceGroupController.java2
-rw-r--r--android/support/v7/util/SortedList.java277
-rw-r--r--android/support/v7/util/SortedListTest.java900
-rw-r--r--android/support/v7/view/ContextThemeWrapper.java22
-rw-r--r--android/support/v7/widget/AppCompatAutoCompleteTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatCheckedTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatEditText.java8
-rw-r--r--android/support/v7/widget/AppCompatHintHelper.java43
-rw-r--r--android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java8
-rw-r--r--android/support/v7/widget/AppCompatTextView.java8
-rw-r--r--android/support/v7/widget/RecyclerView.java120
-rw-r--r--android/support/v7/widget/Toolbar.java13
-rw-r--r--android/support/v7/widget/TooltipPopup.java18
-rw-r--r--android/support/v7/widget/WithHint.java36
-rw-r--r--android/support/wear/ambient/AmbientMode.java43
44 files changed, 1485 insertions, 648 deletions
diff --git a/android/support/LibraryGroups.java b/android/support/LibraryGroups.java
index feaefbc6..19c0a927 100644
--- a/android/support/LibraryGroups.java
+++ b/android/support/LibraryGroups.java
@@ -27,4 +27,5 @@ public class LibraryGroups {
public static final String ARCH_CORE = "android.arch.core";
public static final String PAGING = "android.arch.paging";
public static final String NAVIGATION = "android.arch.navigation";
+ public static final String SLICES = "androidx.app.slice";
}
diff --git a/android/support/car/widget/PagedListView.java b/android/support/car/widget/PagedListView.java
index 4695c45c..67a6247a 100644
--- a/android/support/car/widget/PagedListView.java
+++ b/android/support/car/widget/PagedListView.java
@@ -286,38 +286,6 @@ public class PagedListView extends FrameLayout {
return mLayoutManager.getPosition(v);
}
- private void scroll(int direction) {
- View focusedView = mRecyclerView.getFocusedChild();
- if (focusedView != null) {
- int position = mLayoutManager.getPosition(focusedView);
- int newPosition =
- Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
- if (newPosition != position) {
- // newPosition/position are adapter positions.
- // Convert to layout position by subtracting adapter position of view at layout
- // position 0.
- View childAt = mRecyclerView.getChildAt(
- newPosition - mLayoutManager.getPosition(mLayoutManager.getChildAt(0)));
- if (childAt != null) {
- childAt.requestFocus();
- }
- }
- }
- }
-
- private boolean canScroll(int direction) {
- View focusedView = mRecyclerView.getFocusedChild();
- if (focusedView != null) {
- int position = mLayoutManager.getPosition(focusedView);
- int newPosition =
- Math.max(Math.min(position + direction, mLayoutManager.getItemCount() - 1), 0);
- if (newPosition != position) {
- return true;
- }
- }
- return false;
- }
-
@NonNull
public CarRecyclerView getRecyclerView() {
return mRecyclerView;
diff --git a/android/support/design/widget/BottomSheetBehavior.java b/android/support/design/widget/BottomSheetBehavior.java
index aaa9b804..00ce8f90 100644
--- a/android/support/design/widget/BottomSheetBehavior.java
+++ b/android/support/design/widget/BottomSheetBehavior.java
@@ -559,7 +559,7 @@ public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behav
* Gets the current state of the bottom sheet.
*
* @return One of {@link #STATE_EXPANDED}, {@link #STATE_COLLAPSED}, {@link #STATE_DRAGGING},
- * and {@link #STATE_SETTLING}.
+ * {@link #STATE_SETTLING}, and {@link #STATE_HIDDEN}.
*/
@State
public final int getState() {
diff --git a/android/support/design/widget/CollapsingToolbarLayout.java b/android/support/design/widget/CollapsingToolbarLayout.java
index 0051de9e..8c9b7d49 100644
--- a/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/android/support/design/widget/CollapsingToolbarLayout.java
@@ -44,6 +44,7 @@ import android.support.v4.util.ObjectsCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.AttributeSet;
diff --git a/android/support/design/widget/CoordinatorLayout.java b/android/support/design/widget/CoordinatorLayout.java
index 477a8d62..c45810ef 100644
--- a/android/support/design/widget/CoordinatorLayout.java
+++ b/android/support/design/widget/CoordinatorLayout.java
@@ -56,6 +56,8 @@ import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewCompat.NestedScrollType;
import android.support.v4.view.ViewCompat.ScrollAxis;
import android.support.v4.view.WindowInsetsCompat;
+import android.support.v4.widget.DirectedAcyclicGraph;
+import android.support.v4.widget.ViewGroupUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
diff --git a/android/support/design/widget/DirectedAcyclicGraphTest.java b/android/support/design/widget/DirectedAcyclicGraphTest.java
deleted file mode 100644
index ec7687d5..00000000
--- a/android/support/design/widget/DirectedAcyclicGraphTest.java
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.design.widget;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.support.annotation.NonNull;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.List;
-
-@RunWith(JUnit4.class)
-public class DirectedAcyclicGraphTest {
-
- private DirectedAcyclicGraph<TestNode> mGraph;
-
- @Before
- public void setup() {
- mGraph = new DirectedAcyclicGraph<>();
- }
-
- @Test
- public void test_addNode() {
- final TestNode node = new TestNode("node");
- mGraph.addNode(node);
- assertEquals(1, mGraph.size());
- assertTrue(mGraph.contains(node));
- }
-
- @Test
- public void test_addNodeAgain() {
- final TestNode node = new TestNode("node");
- mGraph.addNode(node);
- mGraph.addNode(node);
-
- assertEquals(1, mGraph.size());
- assertTrue(mGraph.contains(node));
- }
-
- @Test
- public void test_addEdge() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
-
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void test_addEdgeWithNotAddedEdgeNode() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
-
- // Add the node, but not the edge node
- mGraph.addNode(node);
-
- // Now add the link
- mGraph.addEdge(node, edge);
- }
-
- @Test
- public void test_getIncomingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
-
- final List<TestNode> incomingEdges = mGraph.getIncomingEdges(node);
- assertNotNull(incomingEdges);
- assertEquals(1, incomingEdges.size());
- assertEquals(edge, incomingEdges.get(0));
- }
-
- @Test
- public void test_getOutgoingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
- mGraph.addEdge(node, edge);
-
- // Now assert the getOutgoingEdges returns a list which has one element (node)
- final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
- assertNotNull(outgoingEdges);
- assertEquals(1, outgoingEdges.size());
- assertTrue(outgoingEdges.contains(node));
- }
-
- @Test
- public void test_getOutgoingEdgesMultiple() {
- final TestNode node1 = new TestNode("1");
- final TestNode node2 = new TestNode("2");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(edge);
-
- mGraph.addEdge(node1, edge);
- mGraph.addEdge(node2, edge);
-
- // Now assert the getOutgoingEdges returns a list which has 2 elements (node1 & node2)
- final List<TestNode> outgoingEdges = mGraph.getOutgoingEdges(edge);
- assertNotNull(outgoingEdges);
- assertEquals(2, outgoingEdges.size());
- assertTrue(outgoingEdges.contains(node1));
- assertTrue(outgoingEdges.contains(node2));
- }
-
- @Test
- public void test_hasOutgoingEdges() {
- final TestNode node = new TestNode("node");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node);
- mGraph.addNode(edge);
-
- // There is no edge currently and assert that fact
- assertFalse(mGraph.hasOutgoingEdges(edge));
- // Now add the edge
- mGraph.addEdge(node, edge);
- // and assert that the methods returns true;
- assertTrue(mGraph.hasOutgoingEdges(edge));
- }
-
- @Test
- public void test_clear() {
- final TestNode node1 = new TestNode("1");
- final TestNode node2 = new TestNode("2");
- final TestNode edge = new TestNode("edge");
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(edge);
-
- // Now clear the graph
- mGraph.clear();
-
- // Now assert the graph is empty and that contains returns false
- assertEquals(0, mGraph.size());
- assertFalse(mGraph.contains(node1));
- assertFalse(mGraph.contains(node2));
- assertFalse(mGraph.contains(edge));
- }
-
- @Test
- public void test_getSortedList() {
- final TestNode node1 = new TestNode("A");
- final TestNode node2 = new TestNode("B");
- final TestNode node3 = new TestNode("C");
- final TestNode node4 = new TestNode("D");
-
- // Now we'll add the nodes
- mGraph.addNode(node1);
- mGraph.addNode(node2);
- mGraph.addNode(node3);
- mGraph.addNode(node4);
-
- // Now we'll add edges so that 4 <- 2, 2 <- 3, 3 <- 1 (where <- denotes a dependency)
- mGraph.addEdge(node4, node2);
- mGraph.addEdge(node2, node3);
- mGraph.addEdge(node3, node1);
-
- final List<TestNode> sorted = mGraph.getSortedList();
- // Assert that it is the correct size
- assertEquals(4, sorted.size());
- // Assert that all of the nodes are present and in their sorted order
- assertEquals(node1, sorted.get(0));
- assertEquals(node3, sorted.get(1));
- assertEquals(node2, sorted.get(2));
- assertEquals(node4, sorted.get(3));
- }
-
- private static class TestNode {
- private final String mLabel;
-
- TestNode(@NonNull String label) {
- mLabel = label;
- }
-
- @Override
- public String toString() {
- return "TestNode: " + mLabel;
- }
- }
-
-}
diff --git a/android/support/design/widget/FloatingActionButton.java b/android/support/design/widget/FloatingActionButton.java
index b9388366..f37b3798 100644
--- a/android/support/design/widget/FloatingActionButton.java
+++ b/android/support/design/widget/FloatingActionButton.java
@@ -36,6 +36,7 @@ import android.support.annotation.VisibleForTesting;
import android.support.design.R;
import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.widget.AppCompatImageHelper;
import android.util.AttributeSet;
import android.util.Log;
@@ -116,6 +117,11 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
public static final int SIZE_AUTO = -1;
/**
+ * Indicates that FloatingActionButton should not have a custom size.
+ */
+ public static final int NO_CUSTOM_SIZE = 0;
+
+ /**
* The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal.
*/
private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470;
@@ -132,6 +138,7 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
private int mBorderWidth;
private int mRippleColor;
private int mSize;
+ private int mCustomSize;
int mImagePadding;
private int mMaxImageSize;
@@ -164,6 +171,8 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
+ mCustomSize = a.getDimensionPixelSize(R.styleable.FloatingActionButton_fabCustomSize,
+ 0);
mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
final float pressedTranslationZ = a.getDimension(
@@ -430,12 +439,41 @@ public class FloatingActionButton extends VisibilityAwareImageButton {
};
}
+ /**
+ * Sets the size of the button to be a custom value in pixels. If set to
+ * {@link #NO_CUSTOM_SIZE}, custom size will not be used and size will be calculated according
+ * to {@link #setSize(int)} method.
+ *
+ * @param size preferred size in pixels, or zero
+ *
+ * @attr ref android.support.design.R.styleable#FloatingActionButton_fabCustomSize
+ */
+ public void setCustomSize(int size) {
+ if (size < 0) {
+ throw new IllegalArgumentException("Custom size should be non-negative.");
+ }
+ mCustomSize = size;
+ }
+
+ /**
+ * Returns the custom size for this button.
+ *
+ * @return size in pixels, or {@link #NO_CUSTOM_SIZE}
+ */
+ public int getCustomSize() {
+ return mCustomSize;
+ }
+
int getSizeDimension() {
return getSizeDimension(mSize);
}
private int getSizeDimension(@Size final int size) {
final Resources res = getResources();
+ // If custom size is set, return it
+ if (mCustomSize != NO_CUSTOM_SIZE) {
+ return mCustomSize;
+ }
switch (size) {
case SIZE_AUTO:
// If we're set to auto, grab the size from resources and refresh
diff --git a/android/support/design/widget/TextInputEditText.java b/android/support/design/widget/TextInputEditText.java
index 7235ec22..ee6c32cd 100644
--- a/android/support/design/widget/TextInputEditText.java
+++ b/android/support/design/widget/TextInputEditText.java
@@ -18,6 +18,7 @@ package android.support.design.widget;
import android.content.Context;
import android.support.v7.widget.AppCompatEditText;
+import android.support.v7.widget.WithHint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewParent;
@@ -48,12 +49,12 @@ public class TextInputEditText extends AppCompatEditText {
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
final InputConnection ic = super.onCreateInputConnection(outAttrs);
if (ic != null && outAttrs.hintText == null) {
- // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the
+ // If we don't have a hint and our parent implements WithHint, use its hint for the
// EditorInfo. This allows us to display a hint in 'extract mode'.
ViewParent parent = getParent();
while (parent instanceof View) {
- if (parent instanceof TextInputLayout) {
- outAttrs.hintText = ((TextInputLayout) parent).getHint();
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
break;
}
parent = parent.getParent();
diff --git a/android/support/design/widget/TextInputLayout.java b/android/support/design/widget/TextInputLayout.java
index c9e8010d..0540678e 100644
--- a/android/support/design/widget/TextInputLayout.java
+++ b/android/support/design/widget/TextInputLayout.java
@@ -49,10 +49,12 @@ import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.Space;
import android.support.v4.widget.TextViewCompat;
+import android.support.v4.widget.ViewGroupUtils;
import android.support.v7.content.res.AppCompatResources;
import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.AppCompatTextView;
import android.support.v7.widget.TintTypedArray;
+import android.support.v7.widget.WithHint;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
@@ -113,7 +115,7 @@ import android.widget.TextView;
* may not return the TextInputLayout itself, but rather an intermediate View. If you need
* to access a View directly, set an {@code android:id} and use {@link View#findViewById(int)}.
*/
-public class TextInputLayout extends LinearLayout {
+public class TextInputLayout extends LinearLayout implements WithHint {
private static final int ANIMATION_DURATION = 200;
private static final int INVALID_MAX_LENGTH = -1;
@@ -497,6 +499,7 @@ public class TextInputLayout extends LinearLayout {
*
* @attr ref android.support.design.R.styleable#TextInputLayout_android_hint
*/
+ @Override
@Nullable
public CharSequence getHint() {
return mHintEnabled ? mHint : null;
diff --git a/android/support/doclava/DoclavaJavadocOptionFileOption.java b/android/support/doclava/DoclavaJavadocOptionFileOption.java
deleted file mode 100644
index db3f3188..00000000
--- a/android/support/doclava/DoclavaJavadocOptionFileOption.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 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.doclava;
-
-import org.gradle.external.javadoc.internal.AbstractJavadocOptionFileOption;
-import org.gradle.external.javadoc.internal.JavadocOptionFileWriterContext;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-
-/**
- * This class is used to hold complex argument(s) to doclava
- */
-public class DoclavaJavadocOptionFileOption extends
- AbstractJavadocOptionFileOption<Iterable<String>> {
-
- public DoclavaJavadocOptionFileOption(String option) {
- super(option, null);
- }
-
- public DoclavaJavadocOptionFileOption(String option, Iterable<String> value) {
- super(option, value);
- }
-
- @Override
- public void write(JavadocOptionFileWriterContext writerContext) throws IOException {
- writerContext.writeOptionHeader(getOption());
-
- final Iterable<String> args = getValue();
- if (args != null) {
- final Iterator<String> iter = args.iterator();
- while (true) {
- writerContext.writeValue(iter.next());
- if (!iter.hasNext()) {
- break;
- }
- writerContext.write(" ");
- }
- }
-
- writerContext.newLine();
- }
-
- /**
- * @return a deep copy of the option
- */
- public DoclavaJavadocOptionFileOption duplicate() {
- final Iterable<String> value = getValue();
- final ArrayList<String> valueCopy;
- if (value != null) {
- valueCopy = new ArrayList<>();
- for (String item : value) {
- valueCopy.add(item);
- }
- } else {
- valueCopy = null;
- }
- return new DoclavaJavadocOptionFileOption(getOption(), valueCopy);
- }
-}
diff --git a/android/support/graphics/drawable/VectorDrawableCompat.java b/android/support/graphics/drawable/VectorDrawableCompat.java
index 2c7ae41c..a34fe2b8 100644
--- a/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -173,6 +173,10 @@ import java.util.Stack;
* <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd>
* <dt><code>android:strokeMiterLimit</code></dt>
* <dd>Sets the Miter limit for a stroked path. Default is 4.</dd>
+ * <dt><code>android:fillType</code></dt>
+ * <dd>Sets the fillType for a path. The types can be either "evenOdd" or "nonZero". They behave the
+ * same as SVG's "fill-rule" properties. Default is nonZero. For more details, see
+ * <a href="https://www.w3.org/TR/SVG/painting.html#FillRuleProperty">FillRuleProperty</a></dd>
* </dl></dd>
* </dl>
*
diff --git a/android/support/mediacompat/testlib/MediaBrowserConstants.java b/android/support/mediacompat/testlib/MediaBrowserConstants.java
index 86024d90..f961308d 100644
--- a/android/support/mediacompat/testlib/MediaBrowserConstants.java
+++ b/android/support/mediacompat/testlib/MediaBrowserConstants.java
@@ -31,14 +31,16 @@ public class MediaBrowserConstants {
public static final int SET_SESSION_TOKEN = 7;
public static final String MEDIA_ID_ROOT = "test_media_id_root";
-
- public static final String EXTRAS_KEY = "test_extras_key";
- public static final String EXTRAS_VALUE = "test_extras_value";
-
public static final String MEDIA_ID_INVALID = "test_media_id_invalid";
public static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
public static final String MEDIA_ID_ON_LOAD_ITEM_NOT_IMPLEMENTED =
"test_media_id_on_load_item_not_implemented";
+ public static final String MEDIA_ID_INCLUDE_METADATA = "test_media_id_include_metadata";
+
+ public static final String EXTRAS_KEY = "test_extras_key";
+ public static final String EXTRAS_VALUE = "test_extras_value";
+
+ public static final String MEDIA_METADATA = "test_media_metadata";
public static final String SEARCH_QUERY = "children_2";
public static final String SEARCH_QUERY_FOR_NO_RESULT = "query no result";
diff --git a/android/support/mediacompat/testlib/MediaControllerConstants.java b/android/support/mediacompat/testlib/MediaControllerConstants.java
index 5fa086b3..49788882 100644
--- a/android/support/mediacompat/testlib/MediaControllerConstants.java
+++ b/android/support/mediacompat/testlib/MediaControllerConstants.java
@@ -26,6 +26,8 @@ public class MediaControllerConstants {
public static final int ADD_QUEUE_ITEM = 202;
public static final int ADD_QUEUE_ITEM_WITH_INDEX = 203;
public static final int REMOVE_QUEUE_ITEM = 204;
+ public static final int SET_VOLUME_TO = 205;
+ public static final int ADJUST_VOLUME = 206;
// TransportControls methods.
public static final int PLAY = 301;
diff --git a/android/support/mediacompat/testlib/MediaSessionConstants.java b/android/support/mediacompat/testlib/MediaSessionConstants.java
index cbdccc1b..c0a64d4d 100644
--- a/android/support/mediacompat/testlib/MediaSessionConstants.java
+++ b/android/support/mediacompat/testlib/MediaSessionConstants.java
@@ -51,6 +51,8 @@ public class MediaSessionConstants {
public static final long TEST_QUEUE_ID_2 = 20L;
public static final String TEST_MEDIA_ID_1 = "media_id_1";
public static final String TEST_MEDIA_ID_2 = "media_id_2";
+ public static final String TEST_MEDIA_TITLE_1 = "media_title_1";
+ public static final String TEST_MEDIA_TITLE_2 = "media_title_2";
public static final long TEST_ACTION = 55L;
public static final int TEST_ERROR_CODE = 0x3;
diff --git a/android/support/mediacompat/testlib/VersionConstants.java b/android/support/mediacompat/testlib/VersionConstants.java
index 6533ee17..4b217b10 100644
--- a/android/support/mediacompat/testlib/VersionConstants.java
+++ b/android/support/mediacompat/testlib/VersionConstants.java
@@ -22,4 +22,7 @@ package android.support.mediacompat.testlib;
public class VersionConstants {
public static final String KEY_CLIENT_VERSION = "client_version";
public static final String KEY_SERVICE_VERSION = "service_version";
+
+ public static final String VERSION_TOT = "tot";
+ public static final String VERSION_PREVIOUS = "previous";
}
diff --git a/android/support/mediacompat/testlib/util/IntentUtil.java b/android/support/mediacompat/testlib/util/IntentUtil.java
index bbf97524..8d58a6ff 100644
--- a/android/support/mediacompat/testlib/util/IntentUtil.java
+++ b/android/support/mediacompat/testlib/util/IntentUtil.java
@@ -30,12 +30,13 @@ import java.util.ArrayList;
*/
public class IntentUtil {
+ public static final String SERVICE_PACKAGE_NAME = "android.support.mediacompat.service.test";
+ public static final String CLIENT_PACKAGE_NAME = "android.support.mediacompat.client.test";
+
public static final ComponentName SERVICE_RECEIVER_COMPONENT_NAME = new ComponentName(
- "android.support.mediacompat.service.test",
- "android.support.mediacompat.service.ServiceBroadcastReceiver");
+ SERVICE_PACKAGE_NAME, "android.support.mediacompat.service.ServiceBroadcastReceiver");
public static final ComponentName CLIENT_RECEIVER_COMPONENT_NAME = new ComponentName(
- "android.support.mediacompat.client.test",
- "android.support.mediacompat.client.ClientBroadcastReceiver");
+ CLIENT_PACKAGE_NAME, "android.support.mediacompat.client.ClientBroadcastReceiver");
public static final String ACTION_CALL_MEDIA_BROWSER_SERVICE_METHOD =
"android.support.mediacompat.service.action.CALL_MEDIA_BROWSER_SERVICE_METHOD";
diff --git a/android/support/text/emoji/EmojiCompat.java b/android/support/text/emoji/EmojiCompat.java
index f258c12d..5436aa20 100644
--- a/android/support/text/emoji/EmojiCompat.java
+++ b/android/support/text/emoji/EmojiCompat.java
@@ -221,6 +221,7 @@ public class EmojiCompat {
*
* @see EmojiCompat.Config
*/
+ @SuppressWarnings("GuardedBy")
public static EmojiCompat init(@NonNull final Config config) {
if (sInstance == null) {
synchronized (sInstanceLock) {
@@ -238,6 +239,7 @@ public class EmojiCompat {
*
* @hide
*/
+ @SuppressWarnings("GuardedBy")
@RestrictTo(LIBRARY_GROUP)
@VisibleForTesting
public static EmojiCompat reset(@NonNull final Config config) {
@@ -252,6 +254,7 @@ public class EmojiCompat {
*
* @hide
*/
+ @SuppressWarnings("GuardedBy")
@RestrictTo(LIBRARY_GROUP)
@VisibleForTesting
public static EmojiCompat reset(final EmojiCompat emojiCompat) {
diff --git a/android/support/text/emoji/MetadataListReader.java b/android/support/text/emoji/MetadataListReader.java
index 6034726d..1008c171 100644
--- a/android/support/text/emoji/MetadataListReader.java
+++ b/android/support/text/emoji/MetadataListReader.java
@@ -275,7 +275,7 @@ class MetadataListReader {
@Override
public void skip(int numOfBytes) throws IOException {
while (numOfBytes > 0) {
- long skipped = mInputStream.skip(numOfBytes);
+ int skipped = (int) mInputStream.skip(numOfBytes);
if (skipped < 1) {
throw new IOException("Skip didn't move at least 1 byte forward");
}
diff --git a/android/support/text/emoji/widget/EmojiEditableFactory.java b/android/support/text/emoji/widget/EmojiEditableFactory.java
index 9793c9da..20cde4f9 100644
--- a/android/support/text/emoji/widget/EmojiEditableFactory.java
+++ b/android/support/text/emoji/widget/EmojiEditableFactory.java
@@ -55,6 +55,7 @@ final class EmojiEditableFactory extends Editable.Factory {
}
}
+ @SuppressWarnings("GuardedBy")
public static Editable.Factory getInstance() {
if (sInstance == null) {
synchronized (sInstanceLock) {
diff --git a/android/support/v17/leanback/widget/GridLayoutManager.java b/android/support/v17/leanback/widget/GridLayoutManager.java
index d7020e91..9d159eca 100644
--- a/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2726,40 +2726,6 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
- // Observer is registered on Adapter to invalidate saved instance state
- final RecyclerView.AdapterDataObserver mObServer = new RecyclerView.AdapterDataObserver() {
- @Override
- public void onChanged() {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeChanged(int positionStart, int itemCount) {
- if (DEBUG) {
- Log.v(getTag(), "onItemRangeChanged positionStart "
- + positionStart + " itemCount " + itemCount);
- }
- for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
- mChildrenStates.remove(i);
- }
- }
-
- @Override
- public void onItemRangeInserted(int positionStart, int itemCount) {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
- mChildrenStates.clear();
- }
-
- @Override
- public void onItemRangeRemoved(int positionStart, int itemCount) {
- mChildrenStates.clear();
- }
- };
-
@Override
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
@@ -2771,12 +2737,14 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
+ mChildrenStates.clear();
}
@Override
public void onItemsChanged(RecyclerView recyclerView) {
if (DEBUG) Log.v(getTag(), "onItemsChanged");
mFocusPositionOffset = 0;
+ mChildrenStates.clear();
}
@Override
@@ -2797,6 +2765,7 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
}
}
}
+ mChildrenStates.clear();
}
@Override
@@ -2817,6 +2786,16 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPositionOffset += itemCount;
}
}
+ mChildrenStates.clear();
+ }
+
+ @Override
+ public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
+ if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
+ + positionStart + " itemCount " + itemCount);
+ for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
+ mChildrenStates.remove(i);
+ }
}
@Override
@@ -3515,16 +3494,12 @@ final class GridLayoutManager extends RecyclerView.LayoutManager {
mFocusPosition = NO_POSITION;
mFocusPositionOffset = 0;
mChildrenStates.clear();
- oldAdapter.unregisterAdapterDataObserver(mObServer);
}
if (newAdapter instanceof FacetProviderAdapter) {
mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
} else {
mFacetProviderAdapter = null;
}
- if (newAdapter != null) {
- newAdapter.registerAdapterDataObserver(mObServer);
- }
super.onAdapterChanged(oldAdapter, newAdapter);
}
diff --git a/android/support/v4/graphics/TypefaceCompatApi26Impl.java b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
index 00e31a1a..f23ac0d4 100644
--- a/android/support/v4/graphics/TypefaceCompatApi26Impl.java
+++ b/android/support/v4/graphics/TypefaceCompatApi26Impl.java
@@ -189,10 +189,9 @@ public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
/**
* Call FontFamily#abortCreation()
*/
- private boolean abortCreation(Object family) {
+ private void abortCreation(Object family) {
try {
- Boolean result = (Boolean) mAbortCreation.invoke(family);
- return result.booleanValue();
+ mAbortCreation.invoke(family);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
diff --git a/android/support/v4/media/MediaBrowserCompat.java b/android/support/v4/media/MediaBrowserCompat.java
index 7adf7d78..1b279253 100644
--- a/android/support/v4/media/MediaBrowserCompat.java
+++ b/android/support/v4/media/MediaBrowserCompat.java
@@ -41,10 +41,12 @@ import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_EXTRAS;
import static android.support.v4.media.MediaBrowserProtocol.DATA_SEARCH_QUERY;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SESSION_BINDER;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_VERSION_2;
import android.content.ComponentName;
import android.content.Context;
@@ -1581,6 +1583,7 @@ public final class MediaBrowserCompat {
protected final CallbackHandler mHandler = new CallbackHandler(this);
private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+ protected int mServiceVersion;
protected ServiceBinderWrapper mServiceBinderWrapper;
protected Messenger mCallbacksMessenger;
private MediaSessionCompat.Token mMediaSessionToken;
@@ -1850,6 +1853,7 @@ public final class MediaBrowserCompat {
if (extras == null) {
return;
}
+ mServiceVersion = extras.getInt(EXTRA_SERVICE_VERSION, 0);
IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
if (serviceBinder != null) {
mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
@@ -1956,7 +1960,9 @@ public final class MediaBrowserCompat {
@Override
public void subscribe(@NonNull String parentId, @Nullable Bundle options,
@NonNull SubscriptionCallback callback) {
- if (mServiceBinderWrapper == null) {
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (options == null) {
MediaBrowserCompatApi21.subscribe(
mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
@@ -1971,7 +1977,9 @@ public final class MediaBrowserCompat {
@Override
public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
- if (mServiceBinderWrapper == null) {
+ // From service v2, we use compat code when subscribing.
+ // This is to prevent ClassNotFoundException when options has Parcelable in it.
+ if (mServiceBinderWrapper == null || mServiceVersion < SERVICE_VERSION_2) {
if (callback == null) {
MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
} else {
diff --git a/android/support/v4/media/MediaBrowserProtocol.java b/android/support/v4/media/MediaBrowserProtocol.java
index 7c23d261..8ed152df 100644
--- a/android/support/v4/media/MediaBrowserProtocol.java
+++ b/android/support/v4/media/MediaBrowserProtocol.java
@@ -45,7 +45,15 @@ class MediaBrowserProtocol {
* MediaBrowserServiceCompat.
*/
public static final int SERVICE_VERSION_1 = 1;
- public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+ /**
+ * To prevent ClassNotFoundException from Parcelables, MediaBrowser(Service)Compat tries to
+ * avoid using framework code as much as possible (b/62648808). For backward compatibility,
+ * service v2 is introduced so that the browser can distinguish whether the service supports
+ * subscribing through compat code.
+ */
+ public static final int SERVICE_VERSION_2 = 2;
+ public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_2;
/*
* Messages sent from the media browser service compat to the media browser compat.
diff --git a/android/support/v4/media/MediaBrowserServiceCompat.java b/android/support/v4/media/MediaBrowserServiceCompat.java
index debc66e8..27bf0e30 100644
--- a/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -278,28 +278,8 @@ public abstract class MediaBrowserServiceCompat extends Service {
@Override
public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (mMessenger == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (IBinder binder : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(binder);
- List<Pair<IBinder, Bundle>> callbackList =
- connection.subscriptions.get(parentId);
- if (callbackList != null) {
- for (Pair<IBinder, Bundle> callback : callbackList) {
- if (MediaBrowserCompatUtils.hasDuplicatedItems(
- options, callback.second)) {
- performLoadChildren(parentId, connection, callback.second);
- }
- }
- }
- }
- }
- });
- }
+ notifyChildrenChangedForFramework(parentId, options);
+ notifyChildrenChangedForCompat(parentId, options);
}
@Override
@@ -373,6 +353,31 @@ public abstract class MediaBrowserServiceCompat extends Service {
};
MediaBrowserServiceCompat.this.onLoadChildren(parentId, result);
}
+
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ }
+
+ void notifyChildrenChangedForCompat(final String parentId, final Bundle options) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ List<Pair<IBinder, Bundle>> callbackList =
+ connection.subscriptions.get(parentId);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (MediaBrowserCompatUtils.hasDuplicatedItems(
+ options, callback.second)) {
+ performLoadChildren(parentId, connection, callback.second);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
}
@RequiresApi(23)
@@ -421,20 +426,6 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
@Override
- public void notifyChildrenChanged(final String parentId, final Bundle options) {
- if (mMessenger == null) {
- if (options == null) {
- MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
- } else {
- MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
- options);
- }
- } else {
- super.notifyChildrenChanged(parentId, options);
- }
- }
-
- @Override
public void onLoadChildren(String parentId,
final MediaBrowserServiceCompatApi26.ResultWrapper resultWrapper, Bundle options) {
final Result<List<MediaBrowserCompat.MediaItem>> result
@@ -470,6 +461,16 @@ public abstract class MediaBrowserServiceCompat extends Service {
}
return MediaBrowserServiceCompatApi26.getBrowserRootHints(mServiceObj);
}
+
+ @Override
+ void notifyChildrenChangedForFramework(final String parentId, final Bundle options) {
+ if (options != null) {
+ MediaBrowserServiceCompatApi26.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ } else {
+ super.notifyChildrenChangedForFramework(parentId, options);
+ }
+ }
}
private final class ServiceHandler extends Handler {
diff --git a/android/support/v4/view/NestedScrollingParent2.java b/android/support/v4/view/NestedScrollingParent2.java
index db41c461..2ab463ec 100644
--- a/android/support/v4/view/NestedScrollingParent2.java
+++ b/android/support/v4/view/NestedScrollingParent2.java
@@ -18,7 +18,6 @@
package android.support.v4.view;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat.NestedScrollType;
import android.support.v4.view.ViewCompat.ScrollAxis;
import android.view.MotionEvent;
@@ -144,7 +143,7 @@ public interface NestedScrollingParent2 extends NestedScrollingParent {
* @param consumed Output. The horizontal and vertical scroll distance consumed by this parent
* @param type the type of input which cause this scroll event
*/
- void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
+ void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
@NestedScrollType int type);
}
diff --git a/android/support/v4/view/PagerAdapter.java b/android/support/v4/view/PagerAdapter.java
index a8fb099c..af8e076f 100644
--- a/android/support/v4/view/PagerAdapter.java
+++ b/android/support/v4/view/PagerAdapter.java
@@ -131,6 +131,7 @@ public abstract class PagerAdapter {
/**
* Called to inform the adapter of which item is currently considered to
* be the "primary", that is the one show to the user as the current page.
+ * This method will not be invoked when the adapter contains no items.
*
* @param container The containing View from which the page will be removed.
* @param position The page position that is now the primary.
diff --git a/android/support/v4/view/ViewPager.java b/android/support/v4/view/ViewPager.java
index 36d8696c..350fe955 100644
--- a/android/support/v4/view/ViewPager.java
+++ b/android/support/v4/view/ViewPager.java
@@ -1224,6 +1224,8 @@ public class ViewPager extends ViewGroup {
}
calculatePageOffsets(curItem, curIndex, oldCurInfo);
+
+ mAdapter.setPrimaryItem(this, mCurItem, curItem.object);
}
if (DEBUG) {
@@ -1233,8 +1235,6 @@ public class ViewPager extends ViewGroup {
}
}
- mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
-
mAdapter.finishUpdate(this);
// Check width measurement of current pages and drawing sort order.
diff --git a/android/support/design/widget/DirectedAcyclicGraph.java b/android/support/v4/widget/DirectedAcyclicGraph.java
index 85a32cd5..83c62c06 100644
--- a/android/support/design/widget/DirectedAcyclicGraph.java
+++ b/android/support/v4/widget/DirectedAcyclicGraph.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright 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.
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package android.support.design.widget;
+package android.support.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
import android.support.v4.util.Pools;
import android.support.v4.util.SimpleArrayMap;
@@ -27,8 +30,13 @@ import java.util.List;
/**
* A class which represents a simple directed acyclic graph.
+ *
+ * @param <T> Class for the data objects of this graph.
+ *
+ * @hide
*/
-final class DirectedAcyclicGraph<T> {
+@RestrictTo(LIBRARY)
+public final class DirectedAcyclicGraph<T> {
private final Pools.Pool<ArrayList<T>> mListPool = new Pools.SimplePool<>(10);
private final SimpleArrayMap<T, ArrayList<T>> mGraph = new SimpleArrayMap<>();
@@ -42,7 +50,7 @@ final class DirectedAcyclicGraph<T> {
*
* @param node the node to add
*/
- void addNode(@NonNull T node) {
+ public void addNode(@NonNull T node) {
if (!mGraph.containsKey(node)) {
mGraph.put(node, null);
}
@@ -51,7 +59,7 @@ final class DirectedAcyclicGraph<T> {
/**
* Returns true if the node is already present in the graph, false otherwise.
*/
- boolean contains(@NonNull T node) {
+ public boolean contains(@NonNull T node) {
return mGraph.containsKey(node);
}
@@ -64,7 +72,7 @@ final class DirectedAcyclicGraph<T> {
* @param node the parent node
* @param incomingEdge the node which has is an incoming edge to {@code node}
*/
- void addEdge(@NonNull T node, @NonNull T incomingEdge) {
+ public void addEdge(@NonNull T node, @NonNull T incomingEdge) {
if (!mGraph.containsKey(node) || !mGraph.containsKey(incomingEdge)) {
throw new IllegalArgumentException("All nodes must be present in the graph before"
+ " being added as an edge");
@@ -86,7 +94,7 @@ final class DirectedAcyclicGraph<T> {
* @return a list containing any incoming edges, or null if there are none.
*/
@Nullable
- List getIncomingEdges(@NonNull T node) {
+ public List getIncomingEdges(@NonNull T node) {
return mGraph.get(node);
}
@@ -97,7 +105,7 @@ final class DirectedAcyclicGraph<T> {
* @return a list containing any outgoing edges, or null if there are none.
*/
@Nullable
- List<T> getOutgoingEdges(@NonNull T node) {
+ public List<T> getOutgoingEdges(@NonNull T node) {
ArrayList<T> result = null;
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
@@ -111,7 +119,14 @@ final class DirectedAcyclicGraph<T> {
return result;
}
- boolean hasOutgoingEdges(@NonNull T node) {
+ /**
+ * Checks whether we have any outgoing edges for the given node (i.e. nodes which have
+ * an incoming edge from the given node).
+ *
+ * @return <code>true</code> if the node has any outgoing edges, <code>false</code>
+ * otherwise.
+ */
+ public boolean hasOutgoingEdges(@NonNull T node) {
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
if (edges != null && edges.contains(node)) {
@@ -124,7 +139,7 @@ final class DirectedAcyclicGraph<T> {
/**
* Clears the internal graph, and releases resources to pools.
*/
- void clear() {
+ public void clear() {
for (int i = 0, size = mGraph.size(); i < size; i++) {
ArrayList<T> edges = mGraph.valueAt(i);
if (edges != null) {
@@ -143,7 +158,7 @@ final class DirectedAcyclicGraph<T> {
* of the graph. The node at the end of the list will have no dependencies on other nodes.</p>
*/
@NonNull
- ArrayList<T> getSortedList() {
+ public ArrayList<T> getSortedList() {
mSortResult.clear();
mSortTmpMarked.clear();
@@ -198,4 +213,4 @@ final class DirectedAcyclicGraph<T> {
list.clear();
mListPool.release(list);
}
-} \ No newline at end of file
+}
diff --git a/android/support/design/widget/ViewGroupUtils.java b/android/support/v4/widget/ViewGroupUtils.java
index 5d8b5c74..986b4c20 100644
--- a/android/support/design/widget/ViewGroupUtils.java
+++ b/android/support/v4/widget/ViewGroupUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright 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.
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-package android.support.design.widget;
+package android.support.v4.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.support.annotation.RestrictTo;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
-class ViewGroupUtils {
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public class ViewGroupUtils {
private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
@@ -65,7 +72,7 @@ class ViewGroupUtils {
* @param descendant descendant view to reference
* @param out rect to set to the bounds of the descendant view
*/
- static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
+ public static void getDescendantRect(ViewGroup parent, View descendant, Rect out) {
out.set(0, 0, descendant.getWidth(), descendant.getHeight());
offsetDescendantRect(parent, descendant, out);
}
diff --git a/android/support/v7/preference/CollapsiblePreferenceGroupController.java b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
index e15ca18f..b63ff75b 100644
--- a/android/support/v7/preference/CollapsiblePreferenceGroupController.java
+++ b/android/support/v7/preference/CollapsiblePreferenceGroupController.java
@@ -166,7 +166,7 @@ final class CollapsiblePreferenceGroupController
CharSequence summary = null;
for (int i = collapsedIndex; i < flattenedPreferenceList.size(); i++) {
final Preference preference = flattenedPreferenceList.get(i);
- if (preference instanceof PreferenceGroup) {
+ if (preference instanceof PreferenceGroup || !preference.isVisible()) {
continue;
}
final CharSequence title = preference.getTitle();
diff --git a/android/support/v7/util/SortedList.java b/android/support/v7/util/SortedList.java
index af000a1e..bd07b01e 100644
--- a/android/support/v7/util/SortedList.java
+++ b/android/support/v7/util/SortedList.java
@@ -16,6 +16,7 @@
package android.support.v7.util;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import java.lang.reflect.Array;
@@ -51,17 +52,23 @@ public class SortedList<T> {
T[] mData;
/**
- * A copy of the previous list contents used during the merge phase of addAll.
+ * A reference to the previous set of data that is kept during a mutation operation (addAll or
+ * replaceAll).
*/
private T[] mOldData;
+
+ /**
+ * The current index into mOldData that has not yet been processed during a mutation operation
+ * (addAll or replaceAll).
+ */
private int mOldDataStart;
private int mOldDataSize;
/**
- * The size of the valid portion of mData during the merge phase of addAll.
+ * The current index into the new data that has not yet been processed during a mutation
+ * operation (addAll or replaceAll).
*/
- private int mMergedSize;
-
+ private int mNewDataStart;
/**
* The callback instance that controls the behavior of the SortedList and get notified when
@@ -133,7 +140,7 @@ public class SortedList<T> {
* @see Callback#areContentsTheSame(Object, Object)}
*/
public int add(T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
return add(item, true);
}
@@ -142,30 +149,30 @@ public class SortedList<T> {
* except the callback events may be in a different order/granularity since addAll can batch
* them for better performance.
* <p>
- * If allowed, may modify the input array and even take the ownership over it in order
- * to avoid extra memory allocation during sorting and deduplication.
- * </p>
+ * If allowed, will reference the input array during, and possibly after, the operation to avoid
+ * extra memory allocation, in which case you should not continue to reference or modify the
+ * array yourself.
+ * <p>
* @param items Array of items to be added into the list.
- * @param mayModifyInput If true, SortedList is allowed to modify the input.
- * @see SortedList#addAll(Object[] items)
+ * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+ * input array.
+ * @see SortedList#addAll(T[] items)
*/
public void addAll(T[] items, boolean mayModifyInput) {
- throwIfMerging();
+ throwIfInMutationOperation();
if (items.length == 0) {
return;
}
+
if (mayModifyInput) {
addAllInternal(items);
} else {
- T[] copy = (T[]) Array.newInstance(mTClass, items.length);
- System.arraycopy(items, 0, copy, 0, items.length);
- addAllInternal(copy);
+ addAllInternal(copyArray(items));
}
-
}
/**
- * Adds the given items to the list. Does not modify the input.
+ * Adds the given items to the list. Does not modify or retain the input.
*
* @see SortedList#addAll(T[] items, boolean mayModifyInput)
*
@@ -176,7 +183,7 @@ public class SortedList<T> {
}
/**
- * Adds the given items to the list. Does not modify the input.
+ * Adds the given items to the list. Does not modify or retain the input.
*
* @see SortedList#addAll(T[] items, boolean mayModifyInput)
*
@@ -187,27 +194,134 @@ public class SortedList<T> {
addAll(items.toArray(copy), true);
}
- private void addAllInternal(T[] newItems) {
- final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
- if (forceBatchedUpdates) {
- beginBatchedUpdates();
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate.
+ * <p>
+ * If allowed, will reference the input array during, and possibly after, the operation to avoid
+ * extra memory allocation, in which case you should not continue to reference or modify the
+ * array yourself.
+ * <p>
+ * Note: this method does not detect moves or dispatch
+ * {@link ListUpdateCallback#onMoved(int, int)} events. It instead treats moves as a remove
+ * followed by an add and therefore dispatches {@link ListUpdateCallback#onRemoved(int, int)}
+ * and {@link ListUpdateCallback#onRemoved(int, int)} events. See {@link DiffUtil} if you want
+ * your implementation to dispatch move events.
+ * <p>
+ * @param items Array of items to replace current items.
+ * @param mayModifyInput If true, SortedList is allowed to modify and permanently reference the
+ * input array.
+ * @see #replaceAll(T[])
+ */
+ public void replaceAll(@NonNull T[] items, boolean mayModifyInput) {
+ throwIfInMutationOperation();
+
+ if (mayModifyInput) {
+ replaceAllInternal(items);
+ } else {
+ replaceAllInternal(copyArray(items));
}
+ }
- mOldData = mData;
- mOldDataStart = 0;
- mOldDataSize = mSize;
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate. Does not modify or retain the input.
+ *
+ * @see #replaceAll(T[], boolean)
+ *
+ * @param items Array of items to replace current items.
+ */
+ public void replaceAll(@NonNull T... items) {
+ replaceAll(items, false);
+ }
- Arrays.sort(newItems, mCallback); // Arrays.sort is stable.
+ /**
+ * Replaces the current items with the new items, dispatching {@link ListUpdateCallback} events
+ * for each change detected as appropriate. Does not modify or retain the input.
+ *
+ * @see #replaceAll(T[], boolean)
+ *
+ * @param items Array of items to replace current items.
+ */
+ public void replaceAll(@NonNull Collection<T> items) {
+ T[] copy = (T[]) Array.newInstance(mTClass, items.size());
+ replaceAll(items.toArray(copy), true);
+ }
+
+ private void addAllInternal(T[] newItems) {
+ if (newItems.length < 1) {
+ return;
+ }
+
+ final int newSize = sortAndDedup(newItems);
- final int newSize = deduplicate(newItems);
if (mSize == 0) {
mData = newItems;
mSize = newSize;
- mMergedSize = newSize;
mCallback.onInserted(0, newSize);
} else {
merge(newItems, newSize);
}
+ }
+
+ private void replaceAllInternal(@NonNull T[] newData) {
+ final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
+ if (forceBatchedUpdates) {
+ beginBatchedUpdates();
+ }
+
+ mOldDataStart = 0;
+ mOldDataSize = mSize;
+ mOldData = mData;
+
+ mNewDataStart = 0;
+ int newSize = sortAndDedup(newData);
+ mData = (T[]) Array.newInstance(mTClass, newSize);
+
+ while (mNewDataStart < newSize || mOldDataStart < mOldDataSize) {
+ if (mOldDataStart >= mOldDataSize) {
+ int insertIndex = mNewDataStart;
+ int itemCount = newSize - mNewDataStart;
+ System.arraycopy(newData, insertIndex, mData, insertIndex, itemCount);
+ mNewDataStart += itemCount;
+ mSize += itemCount;
+ mCallback.onInserted(insertIndex, itemCount);
+ break;
+ }
+ if (mNewDataStart >= newSize) {
+ int itemCount = mOldDataSize - mOldDataStart;
+ mSize -= itemCount;
+ mCallback.onRemoved(mNewDataStart, itemCount);
+ break;
+ }
+
+ T oldItem = mOldData[mOldDataStart];
+ T newItem = newData[mNewDataStart];
+
+ int result = mCallback.compare(oldItem, newItem);
+ if (result < 0) {
+ replaceAllRemove();
+ } else if (result > 0) {
+ replaceAllInsert(newItem);
+ } else {
+ if (!mCallback.areItemsTheSame(oldItem, newItem)) {
+ // The items aren't the same even though they were supposed to occupy the same
+ // place, so both notify to remove and add an item in the current location.
+ replaceAllRemove();
+ replaceAllInsert(newItem);
+ } else {
+ mData[mNewDataStart] = newItem;
+ mOldDataStart++;
+ mNewDataStart++;
+ if (!mCallback.areContentsTheSame(oldItem, newItem)) {
+ // The item is the same but the contents have changed, so notify that an
+ // onChanged event has occurred.
+ mCallback.onChanged(mNewDataStart - 1, 1,
+ mCallback.getChangePayload(oldItem, newItem));
+ }
+ }
+ }
+ }
mOldData = null;
@@ -216,17 +330,33 @@ public class SortedList<T> {
}
}
+ private void replaceAllInsert(T newItem) {
+ mData[mNewDataStart] = newItem;
+ mNewDataStart++;
+ mSize++;
+ mCallback.onInserted(mNewDataStart - 1, 1);
+ }
+
+ private void replaceAllRemove() {
+ mSize--;
+ mOldDataStart++;
+ mCallback.onRemoved(mNewDataStart, 1);
+ }
+
/**
- * Remove duplicate items, leaving only the last item from each group of "same" items.
- * Move the remaining items to the beginning of the array.
+ * Sorts and removes duplicate items, leaving only the last item from each group of "same"
+ * items. Move the remaining items to the beginning of the array.
*
* @return Number of deduplicated items at the beginning of the array.
*/
- private int deduplicate(T[] items) {
+ private int sortAndDedup(@NonNull T[] items) {
if (items.length == 0) {
- throw new IllegalArgumentException("Input array must be non-empty");
+ return 0;
}
+ // Arrays.sort is stable.
+ Arrays.sort(items, mCallback);
+
// Keep track of the range of equal items at the end of the output.
// Start with the range containing just the first item.
int rangeStart = 0;
@@ -236,9 +366,6 @@ public class SortedList<T> {
T currentItem = items[i];
int compare = mCallback.compare(items[rangeStart], currentItem);
- if (compare > 0) {
- throw new IllegalArgumentException("Input must be sorted in ascending order.");
- }
if (compare == 0) {
// The range of equal items continues, update it.
@@ -278,27 +405,36 @@ public class SortedList<T> {
* This method assumes that newItems are sorted and deduplicated.
*/
private void merge(T[] newData, int newDataSize) {
+ final boolean forceBatchedUpdates = !(mCallback instanceof BatchedCallback);
+ if (forceBatchedUpdates) {
+ beginBatchedUpdates();
+ }
+
+ mOldData = mData;
+ mOldDataStart = 0;
+ mOldDataSize = mSize;
+
final int mergedCapacity = mSize + newDataSize + CAPACITY_GROWTH;
mData = (T[]) Array.newInstance(mTClass, mergedCapacity);
- mMergedSize = 0;
+ mNewDataStart = 0;
int newDataStart = 0;
while (mOldDataStart < mOldDataSize || newDataStart < newDataSize) {
if (mOldDataStart == mOldDataSize) {
// No more old items, copy the remaining new items.
int itemCount = newDataSize - newDataStart;
- System.arraycopy(newData, newDataStart, mData, mMergedSize, itemCount);
- mMergedSize += itemCount;
+ System.arraycopy(newData, newDataStart, mData, mNewDataStart, itemCount);
+ mNewDataStart += itemCount;
mSize += itemCount;
- mCallback.onInserted(mMergedSize - itemCount, itemCount);
+ mCallback.onInserted(mNewDataStart - itemCount, itemCount);
break;
}
if (newDataStart == newDataSize) {
// No more new items, copy the remaining old items.
int itemCount = mOldDataSize - mOldDataStart;
- System.arraycopy(mOldData, mOldDataStart, mData, mMergedSize, itemCount);
- mMergedSize += itemCount;
+ System.arraycopy(mOldData, mOldDataStart, mData, mNewDataStart, itemCount);
+ mNewDataStart += itemCount;
break;
}
@@ -307,36 +443,47 @@ public class SortedList<T> {
int compare = mCallback.compare(oldItem, newItem);
if (compare > 0) {
// New item is lower, output it.
- mData[mMergedSize++] = newItem;
+ mData[mNewDataStart++] = newItem;
mSize++;
newDataStart++;
- mCallback.onInserted(mMergedSize - 1, 1);
+ mCallback.onInserted(mNewDataStart - 1, 1);
} else if (compare == 0 && mCallback.areItemsTheSame(oldItem, newItem)) {
// Items are the same. Output the new item, but consume both.
- mData[mMergedSize++] = newItem;
+ mData[mNewDataStart++] = newItem;
newDataStart++;
mOldDataStart++;
if (!mCallback.areContentsTheSame(oldItem, newItem)) {
- mCallback.onChanged(mMergedSize - 1, 1,
+ mCallback.onChanged(mNewDataStart - 1, 1,
mCallback.getChangePayload(oldItem, newItem));
}
} else {
// Old item is lower than or equal to (but not the same as the new). Output it.
// New item with the same sort order will be inserted later.
- mData[mMergedSize++] = oldItem;
+ mData[mNewDataStart++] = oldItem;
mOldDataStart++;
}
}
+
+ mOldData = null;
+
+ if (forceBatchedUpdates) {
+ endBatchedUpdates();
+ }
}
- private void throwIfMerging() {
+ /**
+ * Throws an exception if called while we are in the middle of a mutation operation (addAll or
+ * replaceAll).
+ */
+ private void throwIfInMutationOperation() {
if (mOldData != null) {
- throw new IllegalStateException("Cannot call this method from within addAll");
+ throw new IllegalStateException("Data cannot be mutated in the middle of a batch "
+ + "update operation such as addAll or replaceAll.");
}
}
/**
- * Batches adapter updates that happen between calling this method until calling
+ * Batches adapter updates that happen after calling this method and before calling
* {@link #endBatchedUpdates()}. For example, if you add multiple items in a loop
* and they are placed into consecutive indices, SortedList calls
* {@link Callback#onInserted(int, int)} only once with the proper item count. If an event
@@ -368,7 +515,7 @@ public class SortedList<T> {
* has no effect.
*/
public void beginBatchedUpdates() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mCallback instanceof BatchedCallback) {
return;
}
@@ -382,7 +529,7 @@ public class SortedList<T> {
* Ends the update transaction and dispatches any remaining event to the callback.
*/
public void endBatchedUpdates() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mCallback instanceof BatchedCallback) {
((BatchedCallback) mCallback).dispatchLastEvent();
}
@@ -424,7 +571,7 @@ public class SortedList<T> {
* @return True if item is removed, false if item cannot be found in the list.
*/
public boolean remove(T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
return remove(item, true);
}
@@ -436,7 +583,7 @@ public class SortedList<T> {
* @return The removed item.
*/
public T removeItemAt(int index) {
- throwIfMerging();
+ throwIfInMutationOperation();
T item = get(index);
removeItemAtIndex(index, true);
return item;
@@ -481,7 +628,7 @@ public class SortedList<T> {
* @see #add(Object)
*/
public void updateItemAt(int index, T item) {
- throwIfMerging();
+ throwIfInMutationOperation();
final T existing = get(index);
// assume changed if the same object is given back
boolean contentsChanged = existing == item || !mCallback.areContentsTheSame(existing, item);
@@ -535,7 +682,7 @@ public class SortedList<T> {
* @see #add(Object)
*/
public void recalculatePositionOfItemAt(int index) {
- throwIfMerging();
+ throwIfInMutationOperation();
// TODO can be improved
final T item = get(index);
removeItemAtIndex(index, false);
@@ -562,8 +709,8 @@ public class SortedList<T> {
if (mOldData != null) {
// The call is made from a callback during addAll execution. The data is split
// between mData and mOldData.
- if (index >= mMergedSize) {
- return mOldData[index - mMergedSize + mOldDataStart];
+ if (index >= mNewDataStart) {
+ return mOldData[index - mNewDataStart + mOldDataStart];
}
}
return mData[index];
@@ -579,13 +726,13 @@ public class SortedList<T> {
*/
public int indexOf(T item) {
if (mOldData != null) {
- int index = findIndexOf(item, mData, 0, mMergedSize, LOOKUP);
+ int index = findIndexOf(item, mData, 0, mNewDataStart, LOOKUP);
if (index != INVALID_POSITION) {
return index;
}
index = findIndexOf(item, mOldData, mOldDataStart, mOldDataSize, LOOKUP);
if (index != INVALID_POSITION) {
- return index - mOldDataStart + mMergedSize;
+ return index - mOldDataStart + mNewDataStart;
}
return INVALID_POSITION;
}
@@ -662,11 +809,17 @@ public class SortedList<T> {
mSize++;
}
+ private T[] copyArray(T[] items) {
+ T[] copy = (T[]) Array.newInstance(mTClass, items.length);
+ System.arraycopy(items, 0, copy, 0, items.length);
+ return copy;
+ }
+
/**
* Removes all items from the SortedList.
*/
public void clear() {
- throwIfMerging();
+ throwIfInMutationOperation();
if (mSize == 0) {
return;
}
@@ -722,8 +875,8 @@ public class SortedList<T> {
* so
* that you can change its behavior depending on your UI.
* <p>
- * For example, if you are using SortedList with a {@link android.support.v7.widget.RecyclerView.Adapter
- * RecyclerView.Adapter}, you should
+ * For example, if you are using SortedList with a
+ * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
* return whether the items' visual representations are the same or not.
*
* @param oldItem The previous representation of the object.
@@ -734,7 +887,7 @@ public class SortedList<T> {
abstract public boolean areContentsTheSame(T2 oldItem, T2 newItem);
/**
- * Called by the SortedList to decide whether two object represent the same Item or not.
+ * Called by the SortedList to decide whether two objects represent the same Item or not.
* <p>
* For example, if your items have unique ids, this method should check their equality.
*
diff --git a/android/support/v7/util/SortedListTest.java b/android/support/v7/util/SortedListTest.java
index 47d2ac0f..f8bc496c 100644
--- a/android/support/v7/util/SortedListTest.java
+++ b/android/support/v7/util/SortedListTest.java
@@ -27,11 +27,15 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
@RunWith(JUnit4.class)
@SmallTest
@@ -44,6 +48,8 @@ public class SortedListTest extends TestCase {
List<Pair> mUpdates = new ArrayList<Pair>();
private boolean mPayloadChanges = false;
List<PayloadChange> mPayloadUpdates = new ArrayList<>();
+ Queue<AssertListStateRunnable> mCallbackRunnables;
+ List<Event> mEvents = new ArrayList<>();
private SortedList.Callback<Item> mCallback;
InsertedCallback<Item> mInsertedCallback;
ChangedCallback<Item> mChangedCallback;
@@ -67,6 +73,7 @@ public class SortedListTest extends TestCase {
@Before
public void setUp() throws Exception {
super.setUp();
+
mCallback = new SortedList.Callback<Item>() {
@Override
public int compare(Item o1, Item o2) {
@@ -75,28 +82,35 @@ public class SortedListTest extends TestCase {
@Override
public void onInserted(int position, int count) {
+ mEvents.add(new Event(TYPE.ADD, position, count));
mAdditions.add(new Pair(position, count));
if (mInsertedCallback != null) {
mInsertedCallback.onInserted(position, count);
}
+ pollAndRun(mCallbackRunnables);
}
@Override
public void onRemoved(int position, int count) {
+ mEvents.add(new Event(TYPE.REMOVE, position, count));
mRemovals.add(new Pair(position, count));
+ pollAndRun(mCallbackRunnables);
}
@Override
public void onMoved(int fromPosition, int toPosition) {
+ mEvents.add(new Event(TYPE.MOVE, fromPosition, toPosition));
mMoves.add(new Pair(fromPosition, toPosition));
}
@Override
public void onChanged(int position, int count) {
+ mEvents.add(new Event(TYPE.CHANGE, position, count));
mUpdates.add(new Pair(position, count));
if (mChangedCallback != null) {
mChangedCallback.onChanged(position, count);
}
+ pollAndRun(mCallbackRunnables);
}
@Override
@@ -110,7 +124,7 @@ public class SortedListTest extends TestCase {
@Override
public boolean areContentsTheSame(Item oldItem, Item newItem) {
- return oldItem.cmpField == newItem.cmpField && oldItem.data == newItem.data;
+ return oldItem.data == newItem.data;
}
@Override
@@ -127,11 +141,41 @@ public class SortedListTest extends TestCase {
return null;
}
};
- mInsertedCallback = null;
- mChangedCallback = null;
mList = new SortedList<Item>(Item.class, mCallback);
}
+ private void pollAndRun(Queue<AssertListStateRunnable> queue) {
+ if (queue != null) {
+ Runnable runnable = queue.poll();
+ assertNotNull(runnable);
+ runnable.run();
+ }
+ }
+
+ @Test
+ public void testValidMethodsDuringOnInsertedCallbackFromEmptyList() {
+
+ final Item[] items =
+ new Item[] {new Item(0), new Item(1), new Item(2)};
+
+ final AtomicInteger atomicInteger = new AtomicInteger(0);
+ mInsertedCallback = new InsertedCallback<Item>() {
+ @Override
+ public void onInserted(int position, int count) {
+ for (int i = 0; i < count; i++) {
+ assertEquals(mList.get(i), items[i]);
+ assertEquals(mList.indexOf(items[i]), i);
+ atomicInteger.incrementAndGet();
+ }
+ }
+ };
+
+ mList.add(items[0]);
+ mList.clear();
+ mList.addAll(items, false);
+ assertEquals(4, atomicInteger.get());
+ }
+
@Test
public void testEmpty() {
assertEquals("empty", mList.size(), 0);
@@ -139,16 +183,16 @@ public class SortedListTest extends TestCase {
@Test
public void testAdd() {
- Item item = new Item();
+ Item item = new Item(1);
assertEquals(insert(item), 0);
assertEquals(size(), 1);
assertTrue(mAdditions.contains(new Pair(0, 1)));
- Item item2 = new Item();
+ Item item2 = new Item(2);
item2.cmpField = item.cmpField + 1;
assertEquals(insert(item2), 1);
assertEquals(size(), 2);
assertTrue(mAdditions.contains(new Pair(1, 1)));
- Item item3 = new Item();
+ Item item3 = new Item(3);
item3.cmpField = item.cmpField - 1;
mAdditions.clear();
assertEquals(insert(item3), 0);
@@ -158,9 +202,8 @@ public class SortedListTest extends TestCase {
@Test
public void testAddDuplicate() {
- Item item = new Item();
- Item item2 = new Item(item.id, item.cmpField);
- item2.data = item.data;
+ Item item = new Item(1);
+ Item item2 = new Item(item.id);
insert(item);
assertEquals(0, insert(item2));
assertEquals(1, size());
@@ -170,7 +213,7 @@ public class SortedListTest extends TestCase {
@Test
public void testRemove() {
- Item item = new Item();
+ Item item = new Item(1);
assertFalse(remove(item));
assertEquals(0, mRemovals.size());
insert(item);
@@ -184,8 +227,8 @@ public class SortedListTest extends TestCase {
@Test
public void testRemove2() {
- Item item = new Item();
- Item item2 = new Item(item.cmpField);
+ Item item = new Item(1);
+ Item item2 = new Item(2, 1, 1);
insert(item);
assertFalse(remove(item2));
assertEquals(0, mRemovals.size());
@@ -218,11 +261,12 @@ public class SortedListTest extends TestCase {
Random random = new Random(System.nanoTime());
List<Item> copy = new ArrayList<Item>();
StringBuilder log = new StringBuilder();
+ int id = 1;
try {
for (int i = 0; i < 10000; i++) {
switch (random.nextInt(3)) {
case 0://ADD
- Item item = new Item();
+ Item item = new Item(id++);
copy.add(item);
insert(item);
log.append("add ").append(item).append("\n");
@@ -241,12 +285,13 @@ public class SortedListTest extends TestCase {
int index = random.nextInt(mList.size());
item = mList.get(index);
// TODO this cannot work
- Item newItem = new Item(item.id, item.cmpField);
- log.append("update ").append(item).append(" to ").append(newItem)
- .append("\n");
+ Item newItem =
+ new Item(item.id, item.cmpField, random.nextInt(1000));
while (newItem.data == item.data) {
newItem.data = random.nextInt(1000);
}
+ log.append("update ").append(item).append(" to ").append(newItem)
+ .append("\n");
int itemIndex = mList.add(newItem);
copy.remove(item);
copy.add(newItem);
@@ -258,10 +303,12 @@ public class SortedListTest extends TestCase {
if (copy.size() > 0) {
int index = random.nextInt(mList.size());
item = mList.get(index);
- Item newItem = new Item(item.id, random.nextInt());
+ Item newItem = new Item(item.id, random.nextInt(), random.nextInt());
mList.updateItemAt(index, newItem);
copy.remove(item);
copy.add(newItem);
+ log.append("update at ").append(index).append(" ").append(item)
+ .append(" to ").append(newItem).append("\n");
}
}
int lastCmp = Integer.MIN_VALUE;
@@ -299,14 +346,21 @@ public class SortedListTest extends TestCase {
Item[] items = new Item[count];
int id = idFrom;
for (int i = 0; i < count; i++) {
- Item item = new Item(id, id);
- item.data = id;
+ Item item = new Item(id);
items[i] = item;
id += idStep;
}
return items;
}
+ private static Item[] createItemsFromInts(int ... ints) {
+ Item[] items = new Item[ints.length];
+ for (int i = ints.length - 1; i >= 0; i--) {
+ items[i] = new Item(ints[i]);
+ }
+ return items;
+ }
+
private static Item[] shuffle(Item[] items) {
Random random = new Random(System.nanoTime());
final int count = items.length;
@@ -493,8 +547,7 @@ public class SortedListTest extends TestCase {
int uniqueId = 0;
for (int cmpField = 0; cmpField < maxCmpField; cmpField++) {
for (int id = 0; id < idsPerCmpField; id++) {
- Item item = new Item(uniqueId++, cmpField);
- item.data = generation;
+ Item item = new Item(uniqueId++, cmpField, generation);
items[index++] = item;
}
}
@@ -548,13 +601,13 @@ public class SortedListTest extends TestCase {
@Test
public void testAddAllStableSort() {
int id = 0;
- Item item = new Item(id++, 0);
+ Item item = new Item(id++, 0, 0);
mList.add(item);
// Create a few items with the same sort order.
Item[] items = new Item[3];
for (int i = 0; i < 3; i++) {
- items[i] = new Item(id++, item.cmpField);
+ items[i] = new Item(id++, item.cmpField, 0);
assertEquals(0, mCallback.compare(item, items[i]));
}
@@ -576,6 +629,7 @@ public class SortedListTest extends TestCase {
item.data = 1;
}
+
mInsertedCallback = new InsertedCallback<Item>() {
@Override
public void onInserted(int position, int count) {
@@ -585,6 +639,7 @@ public class SortedListTest extends TestCase {
assertEquals(i * 2, mList.get(i).id);
}
assertIntegrity(5, "onInserted(" + position + ", " + count + ")");
+
}
};
@@ -639,7 +694,7 @@ public class SortedListTest extends TestCase {
@Override
public void onInserted(int position, int count) {
try {
- mList.add(new Item());
+ mList.add(new Item(1));
fail("add must throw from within a callback");
} catch (IllegalStateException e) {
}
@@ -729,14 +784,14 @@ public class SortedListTest extends TestCase {
@Test
public void testAddExistingItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add an item with the same id but a new data field i.e. send an update
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 1337;
mList.add(twoUpdate);
assertEquals(1, mPayloadUpdates.size());
@@ -750,14 +805,14 @@ public class SortedListTest extends TestCase {
@Test
public void testUpdateItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add an item with the same id but a new data field i.e. send an update
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 1337;
mList.updateItemAt(1, twoUpdate);
assertEquals(1, mPayloadUpdates.size());
@@ -772,16 +827,16 @@ public class SortedListTest extends TestCase {
@Test
public void testAddMultipleExistingItemCallsChangeWithPayload() {
mList.addAll(
- new Item(1, 10),
- new Item(2, 20),
- new Item(3, 30)
+ new Item(1),
+ new Item(2),
+ new Item(3)
);
mPayloadChanges = true;
// add two items with the same ids but a new data fields i.e. send two updates
- final Item twoUpdate = new Item(2, 20);
+ final Item twoUpdate = new Item(2);
twoUpdate.data = 222;
- final Item threeUpdate = new Item(3, 30);
+ final Item threeUpdate = new Item(3);
threeUpdate.data = 333;
mList.addAll(twoUpdate, threeUpdate);
assertEquals(2, mPayloadUpdates.size());
@@ -796,6 +851,648 @@ public class SortedListTest extends TestCase {
assertEquals(3, size());
}
+ @Test
+ public void replaceAll_mayModifyInputFalse_doesNotModify() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(4);
+ Item replacement1 = new Item(3);
+ Item[] replacements = new Item[]{
+ replacement0,
+ replacement1
+ };
+
+ mList.replaceAll(replacements, false);
+
+ assertSame(replacement0, replacements[0]);
+ assertSame(replacement1, replacements[1]);
+ }
+
+ @Test
+ public void replaceAll_varArgs_isEquivalentToDefault() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(3);
+ Item replacement1 = new Item(4);
+
+ mList.replaceAll(replacement0, replacement1);
+
+ assertEquals(mList.get(0), replacement0);
+ assertEquals(mList.get(1), replacement1);
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_collection_isEquivalentToDefaultWithMayModifyInputFalse() {
+ mList.addAll(
+ new Item(1),
+ new Item(2)
+ );
+ Item replacement0 = new Item(4);
+ Item replacement1 = new Item(3);
+ List<Item> replacements = new ArrayList<>();
+ replacements.add(replacement0);
+ replacements.add(replacement1);
+
+ mList.replaceAll(replacements);
+
+ assertEquals(mList.get(0), replacement1);
+ assertEquals(mList.get(1), replacement0);
+ assertSame(replacements.get(0), replacement0);
+ assertSame(replacements.get(1), replacement1);
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_callsChangeWithPayload() {
+ mList.addAll(
+ new Item(1),
+ new Item(2),
+ new Item(3)
+ );
+ mPayloadChanges = true;
+ final Item twoUpdate = new Item(2);
+ twoUpdate.data = 222;
+ final Item threeUpdate = new Item(3);
+ threeUpdate.data = 333;
+
+ mList.replaceAll(twoUpdate, threeUpdate);
+
+ assertEquals(2, mPayloadUpdates.size());
+ final PayloadChange update1 = mPayloadUpdates.get(0);
+ assertEquals(0, update1.position);
+ assertEquals(1, update1.count);
+ assertEquals(222, update1.payload);
+ final PayloadChange update2 = mPayloadUpdates.get(1);
+ assertEquals(1, update2.position);
+ assertEquals(1, update2.count);
+ assertEquals(333, update2.payload);
+ }
+
+ @Test
+ public void replaceAll_totallyEquivalentData_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(1, 2, 3);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mList.replaceAll(items2);
+
+ assertEquals(0, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5);
+ Item[] items2 = createItemsFromInts(2, 4);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 4, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 4);
+ Item[] items2 = createItemsFromInts(1, 3, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5);
+ Item[] items2 = createItemsFromInts(2, 3, 4);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(2, 3, 4, 5)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds4_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 3, 4);
+ Item[] items2 = createItemsFromInts(1, 3, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3, 4)));
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 3)));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds5_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(3, 4, 5);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 2), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_removalsAndAdds6_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(3, 4, 5);
+ Item[] items2 = createItemsFromInts(1, 2, 3);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 2), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 3, 2), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = new Item[]{
+ new Item(2),
+ new Item(3),
+ new Item(1, 4, 1)};
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = new Item[]{
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(2)};
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(9),
+ new Item(7, 10, 7),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3, 0, 3),
+ new Item(1),
+ new Item(5),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move4_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(9, 6, 9),
+ new Item(7),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(3),
+ new Item(1, 4, 1),
+ new Item(5),
+ new Item(9, 6, 9),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 5, 1), mEvents.get(3));
+ assertEquals(4, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_move5_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 3, 5, 7, 9);
+ Item[] items2 = new Item[]{
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3),
+ new Item(1, 9, 1),
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(3),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(7),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3),
+ new Item(9)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(9, 1, 9),
+ new Item(7, 3, 7),
+ new Item(5),
+ new Item(3, 7, 3)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+ assertEquals(new Event(TYPE.ADD, 3, 1), mEvents.get(5));
+ assertEquals(new Event(TYPE.REMOVE, 4, 1), mEvents.get(6));
+ assertEquals(new Event(TYPE.ADD, 4, 1), mEvents.get(7));
+ assertEquals(8, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_orderSameItemDifferent_worksCorrectly() {
+ Item[] items1 = new Item[]{
+ new Item(1),
+ new Item(2, 3, 2),
+ new Item(5)
+ };
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(4, 3, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_orderSameItemSameContentsDifferent_worksCorrectly() {
+ Item[] items1 = new Item[]{
+ new Item(1),
+ new Item(3, 3, 2),
+ new Item(5)
+ };
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(3, 3, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.CHANGE, 1, 1), mEvents.get(0));
+ assertEquals(1, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges1_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(2, 5, 6);
+ Item[] items2 = new Item[]{
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(6, 6, 7)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(createItemsFromInts(1, 5, 6)));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(5),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1),
+ new Item(3, 2, 3),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.ADD, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.REMOVE, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.CHANGE, 2, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges2_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 4, 6);
+ Item[] items2 = new Item[]{
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(5, 4, 5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(4),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(1, 1, 2),
+ new Item(3),
+ new Item(5, 4, 5),
+ new Item(6)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 1, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.REMOVE, 2, 1), mEvents.get(2));
+ assertEquals(new Event(TYPE.ADD, 2, 1), mEvents.get(3));
+ assertEquals(new Event(TYPE.REMOVE, 3, 1), mEvents.get(4));
+ assertEquals(5, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_allTypesOfChanges3_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2);
+ Item[] items2 = new Item[]{
+ new Item(2, 2, 3),
+ new Item(3, 2, 4),
+ new Item(5)
+ };
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(
+ new Item(2, 2, 3)
+ ));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.replaceAll(items2);
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 1), mEvents.get(0));
+ assertEquals(new Event(TYPE.CHANGE, 0, 1), mEvents.get(1));
+ assertEquals(new Event(TYPE.ADD, 1, 2), mEvents.get(2));
+ assertEquals(3, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
+ @Test
+ public void replaceAll_newItemsAreIdentical_resultIsDeduped() {
+ Item[] items = createItemsFromInts(1, 1);
+ mList.replaceAll(items);
+
+ assertEquals(new Item(1), mList.get(0));
+ assertEquals(1, mList.size());
+ }
+
+ @Test
+ public void replaceAll_newItemsUnsorted_resultIsSorted() {
+ Item[] items = createItemsFromInts(2, 1);
+ mList.replaceAll(items);
+
+ assertEquals(new Item(1), mList.get(0));
+ assertEquals(new Item(2), mList.get(1));
+ assertEquals(2, mList.size());
+ }
+
+ @Test
+ public void replaceAll_calledAfterBeginBatchedUpdates_worksCorrectly() {
+ Item[] items1 = createItemsFromInts(1, 2, 3);
+ Item[] items2 = createItemsFromInts(4, 5, 6);
+ mList.addAll(items1);
+ mEvents.clear();
+
+ mCallbackRunnables = new LinkedList<>();
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+ mCallbackRunnables.add(new AssertListStateRunnable(items2));
+
+ mList.beginBatchedUpdates();
+ mList.replaceAll(items2);
+ mList.endBatchedUpdates();
+
+ assertEquals(new Event(TYPE.REMOVE, 0, 3), mEvents.get(0));
+ assertEquals(new Event(TYPE.ADD, 0, 3), mEvents.get(1));
+ assertEquals(2, mEvents.size());
+ assertTrue(sortedListEquals(mList, items2));
+ assertTrue(mCallbackRunnables.isEmpty());
+ }
+
private int size() {
return mList.size();
}
@@ -810,63 +1507,33 @@ public class SortedListTest extends TestCase {
static class Item {
- static int idCounter = 0;
final int id;
-
int cmpField;
+ int data;
- int data = (int) (Math.random() * 1000);//used for comparison
-
- public Item() {
- id = idCounter++;
- cmpField = (int) (Math.random() * 1000);
+ Item(int allFields) {
+ this(allFields, allFields, allFields);
}
- public Item(int cmpField) {
- id = idCounter++;
- this.cmpField = cmpField;
- }
-
- public Item(int id, int cmpField) {
+ Item(int id, int compField, int data) {
this.id = id;
- this.cmpField = cmpField;
+ this.cmpField = compField;
+ this.data = data;
}
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
Item item = (Item) o;
- if (cmpField != item.cmpField) {
- return false;
- }
- if (id != item.id) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = id;
- result = 31 * result + cmpField;
- return result;
+ return id == item.id && cmpField == item.cmpField && data == item.data;
}
@Override
public String toString() {
- return "Item{" +
- "id=" + id +
- ", cmpField=" + cmpField +
- ", data=" + data +
- '}';
+ return "Item(id=" + id + ", cmpField=" + cmpField + ", data=" + data + ')';
}
}
@@ -913,6 +1580,85 @@ public class SortedListTest extends TestCase {
}
}
+ private enum TYPE {
+ ADD, REMOVE, MOVE, CHANGE
+ }
+
+ private final class AssertListStateRunnable implements Runnable {
+
+ private Item[] mExpectedItems;
+
+ AssertListStateRunnable(Item... expectedItems) {
+ this.mExpectedItems = expectedItems;
+ }
+
+ @Override
+ public void run() {
+ try {
+ assertEquals(mExpectedItems.length, mList.size());
+ for (int i = mExpectedItems.length - 1; i >= 0; i--) {
+ assertEquals(mExpectedItems[i], mList.get(i));
+ assertEquals(i, mList.indexOf(mExpectedItems[i]));
+ }
+ } catch (AssertionError assertionError) {
+ throw new AssertionError(
+ assertionError.getMessage()
+ + "\nExpected: "
+ + Arrays.toString(mExpectedItems)
+ + "\nActual: "
+ + sortedListToString(mList));
+ }
+ }
+ }
+
+ private static final class Event {
+ private final TYPE mType;
+ private final int mVal1;
+ private final int mVal2;
+
+ Event(TYPE type, int val1, int val2) {
+ this.mType = type;
+ this.mVal1 = val1;
+ this.mVal2 = val2;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Event that = (Event) o;
+ return mType == that.mType && mVal1 == that.mVal1 && mVal2 == that.mVal2;
+ }
+
+ @Override
+ public String toString() {
+ return "Event(" + mType + ", " + mVal1 + ", " + mVal2 + ")";
+ }
+ }
+
+ private <T> boolean sortedListEquals(SortedList<T> sortedList, T[] array) {
+ if (sortedList.size() != array.length) {
+ return false;
+ }
+ for (int i = sortedList.size() - 1; i >= 0; i--) {
+ if (!sortedList.get(i).equals(array[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String sortedListToString(SortedList sortedList) {
+ StringBuilder stringBuilder = new StringBuilder("[");
+ int size = sortedList.size();
+ for (int i = 0; i < size; i++) {
+ stringBuilder.append(sortedList.get(i).toString() + ", ");
+ }
+ stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
+ stringBuilder.append("]");
+ return stringBuilder.toString();
+ }
+
private static final class PayloadChange {
public final int position;
public final int count;
diff --git a/android/support/v7/view/ContextThemeWrapper.java b/android/support/v7/view/ContextThemeWrapper.java
index aa5b36e9..cc634804 100644
--- a/android/support/v7/view/ContextThemeWrapper.java
+++ b/android/support/v7/view/ContextThemeWrapper.java
@@ -16,26 +16,19 @@
package android.support.v7.view;
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
-import android.support.annotation.RestrictTo;
import android.support.annotation.StyleRes;
import android.support.v7.appcompat.R;
import android.view.LayoutInflater;
/**
- * A ContextWrapper that allows you to modify the theme from what is in the
- * wrapped context.
- *
- * @hide
+ * A context wrapper that allows you to modify or replace the theme of the wrapped context.
*/
-@RestrictTo(LIBRARY_GROUP)
public class ContextThemeWrapper extends ContextWrapper {
private int mThemeResource;
private Resources.Theme mTheme;
@@ -110,15 +103,6 @@ public class ContextThemeWrapper extends ContextWrapper {
mOverrideConfiguration = new Configuration(overrideConfiguration);
}
- /**
- * Used by ActivityThread to apply the overridden configuration to onConfigurationChange
- * callbacks.
- * @hide
- */
- public Configuration getOverrideConfiguration() {
- return mOverrideConfiguration;
- }
-
@Override
public Resources getResources() {
return getResourcesInternal();
@@ -144,6 +128,10 @@ public class ContextThemeWrapper extends ContextWrapper {
}
}
+ /**
+ * Returns the resource ID of the theme that is to be applied on top of the base context's
+ * theme.
+ */
public int getThemeResId() {
return mThemeResource;
}
diff --git a/android/support/v7/widget/AppCompatAutoCompleteTextView.java b/android/support/v7/widget/AppCompatAutoCompleteTextView.java
index 5b0a2f8d..e41bec75 100644
--- a/android/support/v7/widget/AppCompatAutoCompleteTextView.java
+++ b/android/support/v7/widget/AppCompatAutoCompleteTextView.java
@@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.AutoCompleteTextView;
/**
@@ -177,4 +179,10 @@ public class AppCompatAutoCompleteTextView extends AutoCompleteTextView implemen
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatCheckedTextView.java b/android/support/v7/widget/AppCompatCheckedTextView.java
index 921f0a26..dca409c9 100644
--- a/android/support/v7/widget/AppCompatCheckedTextView.java
+++ b/android/support/v7/widget/AppCompatCheckedTextView.java
@@ -20,6 +20,8 @@ import android.content.Context;
import android.support.annotation.DrawableRes;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.CheckedTextView;
/**
@@ -79,4 +81,10 @@ public class AppCompatCheckedTextView extends CheckedTextView {
mTextHelper.applyCompoundDrawablesTints();
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatEditText.java b/android/support/v7/widget/AppCompatEditText.java
index 406e364e..6831fcbf 100644
--- a/android/support/v7/widget/AppCompatEditText.java
+++ b/android/support/v7/widget/AppCompatEditText.java
@@ -28,6 +28,8 @@ import android.support.annotation.RestrictTo;
import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.EditText;
/**
@@ -159,4 +161,10 @@ public class AppCompatEditText extends EditText implements TintableBackgroundVie
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatHintHelper.java b/android/support/v7/widget/AppCompatHintHelper.java
new file mode 100644
index 00000000..0d30fb7c
--- /dev/null
+++ b/android/support/v7/widget/AppCompatHintHelper.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 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.v7.widget;
+
+import android.view.View;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+class AppCompatHintHelper {
+
+ static InputConnection onCreateInputConnection(InputConnection ic, EditorInfo outAttrs,
+ View view) {
+ if (ic != null && outAttrs.hintText == null) {
+ // If we don't have a hint and the parent implements WithHint, use its hint for the
+ // EditorInfo. This allows us to display a hint in 'extract mode'.
+ ViewParent parent = view.getParent();
+ while (parent instanceof View) {
+ if (parent instanceof WithHint) {
+ outAttrs.hintText = ((WithHint) parent).getHint();
+ break;
+ }
+ parent = parent.getParent();
+ }
+ }
+ return ic;
+ }
+
+}
diff --git a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
index 8060d7d1..b71b08a5 100644
--- a/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
+++ b/android/support/v7/widget/AppCompatMultiAutoCompleteTextView.java
@@ -29,6 +29,8 @@ import android.support.v4.view.TintableBackgroundView;
import android.support.v7.appcompat.R;
import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.MultiAutoCompleteTextView;
/**
@@ -177,4 +179,10 @@ public class AppCompatMultiAutoCompleteTextView extends MultiAutoCompleteTextVie
mTextHelper.onSetTextAppearance(context, resId);
}
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/AppCompatTextView.java b/android/support/v7/widget/AppCompatTextView.java
index cfa6a2a9..d8132770 100644
--- a/android/support/v7/widget/AppCompatTextView.java
+++ b/android/support/v7/widget/AppCompatTextView.java
@@ -31,6 +31,8 @@ import android.support.v4.widget.AutoSizeableTextView;
import android.support.v4.widget.TextViewCompat;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
import android.widget.TextView;
/**
@@ -361,4 +363,10 @@ public class AppCompatTextView extends TextView implements TintableBackgroundVie
}
return new int[0];
}
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ return AppCompatHintHelper.onCreateInputConnection(super.onCreateInputConnection(outAttrs),
+ outAttrs, this);
+ }
}
diff --git a/android/support/v7/widget/RecyclerView.java b/android/support/v7/widget/RecyclerView.java
index 4bc17a86..84c28b10 100644
--- a/android/support/v7/widget/RecyclerView.java
+++ b/android/support/v7/widget/RecyclerView.java
@@ -386,8 +386,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
private List<OnChildAttachStateChangeListener> mOnChildAttachStateListeners;
/**
- * Set to true when an adapter data set changed notification is received.
- * In that case, we cannot run any animations since we don't know what happened until layout.
+ * True after an event occurs that signals that the entire data set has changed. In that case,
+ * we cannot run any animations since we don't know what happened until layout.
*
* Attached items are invalid until next layout, at which point layout will animate/replace
* items as necessary, building up content from the (effectively) new adapter from scratch.
@@ -395,11 +395,20 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
* Cached items must be discarded when setting this to true, so that the cache may be freely
* used by prefetching until the next layout occurs.
*
- * @see #setDataSetChangedAfterLayout()
+ * @see #processDataSetCompletelyChanged(boolean)
*/
boolean mDataSetHasChangedAfterLayout = false;
/**
+ * True after the data set has completely changed and
+ * {@link LayoutManager#onItemsChanged(RecyclerView)} should be called during the subsequent
+ * measure/layout.
+ *
+ * @see #processDataSetCompletelyChanged(boolean)
+ */
+ boolean mDispatchItemsChangedEvent = false;
+
+ /**
* This variable is incremented during a dispatchLayout and/or scroll.
* Some methods should not be called during these periods (e.g. adapter data change).
* Doing so will create hard to find bugs so we better check it and throw an exception.
@@ -1044,6 +1053,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, true, removeAndRecycleExistingViews);
+ processDataSetCompletelyChanged(true);
requestLayout();
}
/**
@@ -1059,6 +1069,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
+ processDataSetCompletelyChanged(false);
requestLayout();
}
@@ -1112,7 +1123,6 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
- setDataSetChangedAfterLayout();
}
/**
@@ -2509,9 +2519,17 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (next == null || next == this) {
return false;
}
+ // panic, result view is not a child anymore, maybe workaround b/37864393
+ if (findContainingItemView(next) == null) {
+ return false;
+ }
if (focused == null) {
return true;
}
+ // panic, focused view is not a child anymore, maybe workaround b/37864393
+ if (findContainingItemView(focused) == null) {
+ return true;
+ }
mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
@@ -3221,7 +3239,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Used when onMeasure is called before layout manager is set
+ * An implementation of {@link View#onMeasure(int, int)} to fall back to in various scenarios
+ * where this RecyclerView is otherwise lacking better information.
*/
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
@@ -3398,7 +3417,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
- mLayout.onItemsChanged(this);
+ if (mDispatchItemsChangedEvent) {
+ mLayout.onItemsChanged(this);
+ }
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
@@ -3821,6 +3842,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
+ mDispatchItemsChangedEvent = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
@@ -4288,19 +4310,21 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
viewHolder.getUnmodifiedPayloads());
}
-
/**
- * Call this method to signal that *all* adapter content has changed (generally, because of
- * setAdapter, swapAdapter, or notifyDataSetChanged), and that once layout occurs, all
- * attached items should be discarded or animated.
+ * Processes the fact that, as far as we can tell, the data set has completely changed.
*
- * Attached items are labeled as invalid, and all cached items are discarded.
+ * <ul>
+ * <li>Once layout occurs, all attached items should be discarded or animated.
+ * <li>Attached items are labeled as invalid.
+ * <li>Because items may still be prefetched between a "data set completely changed"
+ * event and a layout event, all cached items are discarded.
+ * </ul>
*
- * It is still possible for items to be prefetched while mDataSetHasChangedAfterLayout == true,
- * so this method must always discard all cached views so that the only valid items that remain
- * in the cache, once layout occurs, are valid prefetched items.
+ * @param dispatchItemsChanged Whether to call
+ * {@link LayoutManager#onItemsChanged(RecyclerView)} during measure/layout.
*/
- void setDataSetChangedAfterLayout() {
+ void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
+ mDispatchItemsChangedEvent |= dispatchItemsChanged;
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
@@ -5110,7 +5134,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
- setDataSetChangedAfterLayout();
+ processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
@@ -7419,9 +7443,10 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
* wants to handle the layout measurements itself.
* <p>
* This method is usually called by the LayoutManager with value {@code true} if it wants
- * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
- * the measurement logic, you can call this method with {@code false} and override
- * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+ * to support {@link ViewGroup.LayoutParams#WRAP_CONTENT}. If you are using a public
+ * LayoutManager but want to customize the measurement logic, you can call this method with
+ * {@code false} and override {@link LayoutManager#onMeasure(Recycler, State, int, int)} to
+ * implement your custom measurement logic.
* <p>
* AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
* handle various specs provided by the RecyclerView's parent.
@@ -7495,24 +7520,26 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Returns whether this LayoutManager supports automatic item animations.
- * A LayoutManager wishing to support item animations should obey certain
- * rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
- * The default return value is <code>false</code>, so subclasses of LayoutManager
- * will not get predictive item animations by default.
- *
- * <p>Whether item animations are enabled in a RecyclerView is determined both
- * by the return value from this method and the
+ * Returns whether this LayoutManager supports "predictive item animations".
+ * <p>
+ * "Predictive item animations" are automatically created animations that show
+ * where items came from, and where they are going to, as items are added, removed,
+ * or moved within a layout.
+ * <p>
+ * A LayoutManager wishing to support predictive item animations must override this
+ * method to return true (the default implementation returns false) and must obey certain
+ * behavioral contracts outlined in {@link #onLayoutChildren(Recycler, State)}.
+ * <p>
+ * Whether item animations actually occur in a RecyclerView is actually determined by both
+ * the return value from this method and the
* {@link RecyclerView#setItemAnimator(ItemAnimator) ItemAnimator} set on the
* RecyclerView itself. If the RecyclerView has a non-null ItemAnimator but this
- * method returns false, then simple item animations will be enabled, in which
- * views that are moving onto or off of the screen are simply faded in/out. If
- * the RecyclerView has a non-null ItemAnimator and this method returns true,
- * then there will be two calls to {@link #onLayoutChildren(Recycler, State)} to
- * setup up the information needed to more intelligently predict where appearing
- * and disappearing views should be animated from/to.</p>
+ * method returns false, then only "simple item animations" will be enabled in the
+ * RecyclerView, in which views whose position are changing are simply faded in/out. If the
+ * RecyclerView has a non-null ItemAnimator and this method returns true, then predictive
+ * item animations will be enabled in the RecyclerView.
*
- * @return true if predictive item animations should be enabled, false otherwise
+ * @return true if this LayoutManager supports predictive item animations, false otherwise.
*/
public boolean supportsPredictiveItemAnimations() {
return false;
@@ -9491,9 +9518,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Called if the RecyclerView this LayoutManager is bound to has a different adapter set.
- * The LayoutManager may use this opportunity to clear caches and configure state such
- * that it can relayout appropriately with the new data and potentially new view types.
+ * Called if the RecyclerView this LayoutManager is bound to has a different adapter set via
+ * {@link RecyclerView#setAdapter(Adapter)} or
+ * {@link RecyclerView#swapAdapter(Adapter, boolean)}. The LayoutManager may use this
+ * opportunity to clear caches and configure state such that it can relayout appropriately
+ * with the new data and potentially new view types.
*
* <p>The default implementation removes all currently attached views.</p>
*
@@ -9535,8 +9564,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Called when {@link Adapter#notifyDataSetChanged()} is triggered instead of giving
- * detailed information on what has actually changed.
+ * Called in response to a call to {@link Adapter#notifyDataSetChanged()} or
+ * {@link RecyclerView#swapAdapter(Adapter, boolean)} ()} and signals that the the entire
+ * data set has changed.
*
* @param recyclerView
*/
@@ -10042,7 +10072,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
if (vScroll == 0 && hScroll == 0) {
return false;
}
- mRecyclerView.scrollBy(hScroll, vScroll);
+ mRecyclerView.smoothScrollBy(hScroll, vScroll);
return true;
}
@@ -11794,6 +11824,11 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
boolean mStructureChanged = false;
+ /**
+ * True if the associated {@link RecyclerView} is in the pre-layout step where it is having
+ * its {@link LayoutManager} layout items where they will be at the beginning of a set of
+ * predictive item animations.
+ */
boolean mInPreLayout = false;
boolean mTrackOldChangeHolders = false;
@@ -11869,8 +11904,9 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro
}
/**
- * Returns true if
- * @return
+ * Returns true if the {@link RecyclerView} is in the pre-layout step where it is having its
+ * {@link LayoutManager} layout items where they will be at the beginning of a set of
+ * predictive item animations.
*/
public boolean isPreLayout() {
return mInPreLayout;
diff --git a/android/support/v7/widget/Toolbar.java b/android/support/v7/widget/Toolbar.java
index 45e25830..f383e90c 100644
--- a/android/support/v7/widget/Toolbar.java
+++ b/android/support/v7/widget/Toolbar.java
@@ -56,6 +56,7 @@ import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
@@ -2366,12 +2367,20 @@ public class Toolbar extends ViewGroup {
@Override
public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
ensureCollapseButtonView();
- if (mCollapseButtonView.getParent() != Toolbar.this) {
+ ViewParent collapseButtonParent = mCollapseButtonView.getParent();
+ if (collapseButtonParent != Toolbar.this) {
+ if (collapseButtonParent instanceof ViewGroup) {
+ ((ViewGroup) collapseButtonParent).removeView(mCollapseButtonView);
+ }
addView(mCollapseButtonView);
}
mExpandedActionView = item.getActionView();
mCurrentExpandedItem = item;
- if (mExpandedActionView.getParent() != Toolbar.this) {
+ ViewParent expandedActionParent = mExpandedActionView.getParent();
+ if (expandedActionParent != Toolbar.this) {
+ if (expandedActionParent instanceof ViewGroup) {
+ ((ViewGroup) expandedActionParent).removeView(mExpandedActionView);
+ }
final LayoutParams lp = generateDefaultLayoutParams();
lp.gravity = GravityCompat.START | (mButtonGravity & Gravity.VERTICAL_GRAVITY_MASK);
lp.mViewType = LayoutParams.EXPANDED;
diff --git a/android/support/v7/widget/TooltipPopup.java b/android/support/v7/widget/TooltipPopup.java
index dc20aa1f..396fe058 100644
--- a/android/support/v7/widget/TooltipPopup.java
+++ b/android/support/v7/widget/TooltipPopup.java
@@ -31,6 +31,7 @@ import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.TextView;
@@ -99,6 +100,7 @@ class TooltipPopup {
private void computePosition(View anchorView, int anchorX, int anchorY, boolean fromTouch,
WindowManager.LayoutParams outParams) {
+ outParams.token = anchorView.getApplicationWindowToken();
final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
R.dimen.tooltip_precise_anchor_threshold);
@@ -157,7 +159,7 @@ class TooltipPopup {
mTmpAnchorPos[1] -= mTmpAppPos[1];
// mTmpAnchorPos is now relative to the main app window.
- outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+ outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
mContentView.measure(spec, spec);
@@ -181,6 +183,16 @@ class TooltipPopup {
}
private static View getAppRootView(View anchorView) {
+ View rootView = anchorView.getRootView();
+ ViewGroup.LayoutParams lp = rootView.getLayoutParams();
+ if (lp instanceof WindowManager.LayoutParams
+ && (((WindowManager.LayoutParams) lp).type
+ == WindowManager.LayoutParams.TYPE_APPLICATION)) {
+ // This covers regular app windows and Dialog windows.
+ return rootView;
+ }
+ // For non-application window types (such as popup windows) try to find the main app window
+ // through the context.
Context context = anchorView.getContext();
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
@@ -189,6 +201,8 @@ class TooltipPopup {
context = ((ContextWrapper) context).getBaseContext();
}
}
- return anchorView.getRootView();
+ // Main app window not found, fall back to the anchor's root view. There is no guarantee
+ // that the tooltip position will be computed correctly.
+ return rootView;
}
}
diff --git a/android/support/v7/widget/WithHint.java b/android/support/v7/widget/WithHint.java
new file mode 100644
index 00000000..d14f483a
--- /dev/null
+++ b/android/support/v7/widget/WithHint.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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.v7.widget;
+
+import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.support.annotation.Nullable;
+import android.support.annotation.RestrictTo;
+
+/**
+ * @hide
+ */
+@RestrictTo(LIBRARY_GROUP)
+public interface WithHint {
+ /**
+ * Returns the hint which is displayed in the floating label, if enabled.
+ *
+ * @return the hint, or null if there isn't one set, or the hint is not enabled.
+ */
+ @Nullable
+ CharSequence getHint();
+}
diff --git a/android/support/wear/ambient/AmbientMode.java b/android/support/wear/ambient/AmbientMode.java
index 5db93830..0077a5bd 100644
--- a/android/support/wear/ambient/AmbientMode.java
+++ b/android/support/wear/ambient/AmbientMode.java
@@ -21,7 +21,9 @@ import android.app.FragmentManager;
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.CallSuper;
+import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
+import android.util.Log;
import com.google.android.wearable.compat.WearableActivityController;
@@ -48,6 +50,7 @@ import java.io.PrintWriter;
* }</pre>
*/
public final class AmbientMode extends Fragment {
+ private static final String TAG = "AmbientMode";
/**
* Property in bundle passed to {@code AmbientCallback#onEnterAmbient(Bundle)} to indicate
@@ -104,9 +107,6 @@ public final class AmbientMode extends Fragment {
* running (after onResume, before onPause). All drawing should complete by the conclusion
* of this method. Note that {@code invalidate()} calls will be executed before resuming
* lower-power mode.
- * <p>
- * <p><em>Derived classes must call through to the super class's implementation of this
- * method. If they do not, an exception will be thrown.</em>
*
* @param ambientDetails bundle containing information about the display being used.
* It includes information about low-bit color and burn-in protection.
@@ -122,9 +122,6 @@ public final class AmbientMode extends Fragment {
/**
* Called when an activity should exit ambient mode. This event is sent while an activity is
* running (after onResume, before onPause).
- * <p>
- * <p><em>Derived classes must call through to the super class's implementation of this
- * method. If they do not, an exception will be thrown.</em>
*/
public void onExitAmbient() {}
}
@@ -133,20 +130,27 @@ public final class AmbientMode extends Fragment {
new AmbientDelegate.AmbientCallback() {
@Override
public void onEnterAmbient(Bundle ambientDetails) {
- mSuppliedCallback.onEnterAmbient(ambientDetails);
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onEnterAmbient(ambientDetails);
+ }
}
@Override
public void onExitAmbient() {
- mSuppliedCallback.onExitAmbient();
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onExitAmbient();
+ }
}
@Override
public void onUpdateAmbient() {
- mSuppliedCallback.onUpdateAmbient();
+ if (mSuppliedCallback != null) {
+ mSuppliedCallback.onUpdateAmbient();
+ }
}
};
private AmbientDelegate mDelegate;
+ @Nullable
private AmbientCallback mSuppliedCallback;
private AmbientController mController;
@@ -166,8 +170,7 @@ public final class AmbientMode extends Fragment {
if (context instanceof AmbientCallbackProvider) {
mSuppliedCallback = ((AmbientCallbackProvider) context).getAmbientCallback();
} else {
- throw new IllegalArgumentException(
- "fragment should attach to an activity that implements AmbientCallback");
+ Log.w(TAG, "No callback provided - enabling only smart resume");
}
}
@@ -176,7 +179,9 @@ public final class AmbientMode extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mDelegate.onCreate();
- mDelegate.setAmbientEnabled();
+ if (mSuppliedCallback != null) {
+ mDelegate.setAmbientEnabled();
+ }
}
@Override
@@ -215,15 +220,19 @@ public final class AmbientMode extends Fragment {
}
/**
- * Attach ambient support to the given activity.
+ * Attach ambient support to the given activity. Calling this method with an Activity
+ * implementing the {@link AmbientCallbackProvider} interface will provide you with an
+ * opportunity to react to ambient events such as {@code onEnterAmbient}. Alternatively,
+ * you can call this method with an Activity which does not implement
+ * the {@link AmbientCallbackProvider} interface and that will only enable the auto-resume
+ * functionality. This is equivalent to providing (@code null} from
+ * the {@link AmbientCallbackProvider}.
*
- * @param activity the activity to attach ambient support to. This activity has to also
- * implement {@link AmbientCallbackProvider}
+ * @param activity the activity to attach ambient support to.
* @return the associated {@link AmbientController} which can be used to query the state of
* ambient mode.
*/
- public static <T extends Activity & AmbientCallbackProvider> AmbientController
- attachAmbientSupport(T activity) {
+ public static <T extends Activity> AmbientController attachAmbientSupport(T activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
AmbientMode ambientFragment = (AmbientMode) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
if (ambientFragment == null) {