Merge "Fix preference dialog double-creation" into mnc-ub-dev
diff --git a/build.gradle b/build.gradle
index 8e89705..45d9901 100644
--- a/build.gradle
+++ b/build.gradle
@@ -118,7 +118,6 @@
createSourceProp.dependsOn unzipRepo
prepareRepo.dependsOn createSourceProp
-
import com.google.common.hash.HashCode
import com.google.common.hash.HashFunction
import com.google.common.hash.Hashing
@@ -169,6 +168,23 @@
project.android.buildToolsVersion = rootProject.buildToolsVersion
}
}
+
+ // Copy instrumentation test APK into the dist dir
+ project.afterEvaluate {
+ def assembleTestTask = project.tasks.findByPath('assembleAndroidTest')
+ if (assembleTestTask != null) {
+ assembleTestTask.doLast {
+ // If the project actually has some instrumentation tests, copy its APK
+ if (!project.android.sourceSets.androidTest.java.sourceFiles.isEmpty()) {
+ def pkgTask = project.tasks.findByPath('packageDebugAndroidTest')
+ copy {
+ from(pkgTask.outputFile)
+ into(rootProject.ext.testApkDistOut)
+ }
+ }
+ }
+ }
+ }
}
project.gradle.buildFinished { buildResult ->
diff --git a/customtabs/tests/NO_DOCS b/customtabs/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/customtabs/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
index 9440282..1a72789 100644
--- a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
+++ b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
@@ -19,10 +19,12 @@
import android.content.Intent;
import android.graphics.Color;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
/**
* Tests for CustomTabsIntent.
*/
+@SmallTest
public class CustomTabsIntentTest extends AndroidTestCase {
public void testBareboneCustomTabIntent() {
diff --git a/design/Android.mk b/design/Android.mk
index 6f4ac3b..98a1f22 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -28,7 +28,8 @@
LOCAL_AAPT_FLAGS := \
--auto-add-overlay \
--extra-packages android.support.v7.appcompat \
- --extra-packages android.support.v7.recyclerview
+ --extra-packages android.support.v7.recyclerview \
+ --no-version-vectors
LOCAL_JAR_EXCLUDE_FILES := none
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/design/build.gradle b/design/build.gradle
index 401ec8e..eca89a6 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -6,11 +6,20 @@
compile project(':support-v4')
compile project(':support-appcompat-v7')
compile project(':support-recyclerview-v7')
+
+ androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
+ androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
+ testCompile 'junit:junit:4.12'
}
android {
compileSdkVersion 'current'
+ defaultConfig {
+ minSdkVersion 7
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDirs = ['base', 'eclair-mr1', 'honeycomb', 'honeycomb-mr1', 'ics', 'lollipop', 'src']
@@ -18,11 +27,10 @@
main.assets.srcDir 'assets'
main.resources.srcDir 'src'
- // this moves src/instrumentTest to tests so all folders follow:
- // tests/java, tests/res, tests/assets, ...
- // This is a *reset* so it replaces the default paths
androidTest.setRoot('tests')
androidTest.java.srcDir 'tests/src'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
compileOptions {
@@ -38,6 +46,10 @@
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
+
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
}
android.libraryVariants.all { variant ->
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
new file mode 100755
index 0000000..c871796
--- /dev/null
+++ b/design/tests/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.design.test">
+
+ <uses-sdk
+ android:minSdkVersion="7"
+ tools:overrideLibrary="android.support.test, android.support.test.espresso,
+ android.support.test.espresso.idling"/>
+
+ <application android:theme="@style/Theme.AppCompat">
+ <uses-library android:name="android.test.runner"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.TabLayoutWithViewPagerActivity"/>
+
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.design.test"/>
+
+</manifest>
diff --git a/design/tests/res/layout/design_tabs_viewpager.xml b/design/tests/res/layout/design_tabs_viewpager.xml
new file mode 100644
index 0000000..7e9728a
--- /dev/null
+++ b/design/tests/res/layout/design_tabs_viewpager.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ android:background="?attr/colorPrimary"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ app:contentInsetStart="72dp"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:background="?attr/colorPrimary"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ app:tabMode="scrollable"
+ app:tabContentStart="72dp"/>
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/tabs_viewpager"
+ android:layout_height="0px"
+ android:layout_width="match_parent"
+ android:layout_weight="1"/>
+
+</LinearLayout>
diff --git a/design/tests/res/values/ids.xml b/design/tests/res/values/ids.xml
new file mode 100644
index 0000000..e5fcf63
--- /dev/null
+++ b/design/tests/res/values/ids.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<resources>
+ <item name="page_0" type="id"/>
+ <item name="page_1" type="id"/>
+ <item name="page_2" type="id"/>
+ <item name="page_3" type="id"/>
+ <item name="page_4" type="id"/>
+ <item name="page_5" type="id"/>
+ <item name="page_6" type="id"/>
+ <item name="page_7" type="id"/>
+ <item name="page_8" type="id"/>
+ <item name="page_9" type="id"/>
+</resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/TabLayoutActions.java b/design/tests/src/android/support/design/testutils/TabLayoutActions.java
new file mode 100644
index 0000000..fb3809e
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TabLayoutActions.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.support.annotation.Nullable;
+import android.support.design.widget.TabLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Tap;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.PagerTitleStrip;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+
+public class TabLayoutActions {
+ /**
+ * Wires <code>TabLayout</code> to <code>ViewPager</code> content.
+ */
+ public static ViewAction setupWithViewPager(final @Nullable ViewPager viewPager) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Setup with ViewPager content";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TabLayout tabLayout = (TabLayout) view;
+ tabLayout.setupWithViewPager(viewPager);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Selects the specified tab in the <code>TabLayout</code>.
+ */
+ public static ViewAction selectTab(final int tabIndex) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Setup with ViewPager content";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TabLayout tabLayout = (TabLayout) view;
+ tabLayout.getTabAt(tabIndex).select();
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/ViewPagerActions.java b/design/tests/src/android/support/design/testutils/ViewPagerActions.java
new file mode 100644
index 0000000..7ab1560
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/ViewPagerActions.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.support.annotation.Nullable;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralClickAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Tap;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.PagerTitleStrip;
+import android.support.v4.view.ViewPager;
+import android.view.View;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+
+public class ViewPagerActions {
+ /**
+ * Moves <code>ViewPager</code> to the right by one page.
+ */
+ public static ViewAction scrollRight() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager scroll one page to the right";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ int current = viewPager.getCurrentItem();
+ viewPager.setCurrentItem(current + 1, false);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the left by one page.
+ */
+ public static ViewAction scrollLeft() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager scroll one page to the left";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ int current = viewPager.getCurrentItem();
+ viewPager.setCurrentItem(current - 1, false);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the last page.
+ */
+ public static ViewAction scrollToLast() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager scroll to last page";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ int size = viewPager.getAdapter().getCount();
+ if (size > 0) {
+ viewPager.setCurrentItem(size - 1, false);
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the first page.
+ */
+ public static ViewAction scrollToFirst() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager scroll to first page";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ int size = viewPager.getAdapter().getCount();
+ if (size > 0) {
+ viewPager.setCurrentItem(0, false);
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to specific page.
+ */
+ public static ViewAction scrollToPage(final int page) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager move one page to the right";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ viewPager.setCurrentItem(page, false);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the specified adapter on <code>ViewPager</code>.
+ */
+ public static ViewAction setAdapter(final @Nullable PagerAdapter adapter) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(ViewPager.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager set adapter";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ viewPager.setAdapter(adapter);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to specific page.
+ */
+ public static ViewAction notifyAdapterContentChange() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(ViewPager.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager notify on adapter content change";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ viewPager.getAdapter().notifyDataSetChanged();
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java b/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
new file mode 100644
index 0000000..bda213c
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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 android.app.Activity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseInstrumentationTestCase<A extends Activity>
+ extends ActivityInstrumentationTestCase2<A> {
+
+ protected BaseInstrumentationTestCase(Class<A> activityClass) {
+ super(activityClass);
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ getActivity();
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BaseTestActivity.java b/design/tests/src/android/support/design/widget/BaseTestActivity.java
new file mode 100755
index 0000000..164a191
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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 android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.view.WindowManager;
+
+abstract class BaseTestActivity extends AppCompatActivity {
+
+ private boolean mDestroyed;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ final int contentView = getContentViewLayoutResId();
+ if (contentView > 0) {
+ setContentView(contentView);
+ }
+ onContentViewSet();
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ protected abstract int getContentViewLayoutResId();
+
+ protected void onContentViewSet() {}
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDestroyed = true;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
new file mode 100644
index 0000000..6fe99a5
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
@@ -0,0 +1,18 @@
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.support.v7.widget.Toolbar;
+
+public class TabLayoutWithViewPagerActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.design_tabs_viewpager;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ setSupportActionBar(toolbar);
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
new file mode 100755
index 0000000..4df12b7
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2015 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 android.graphics.Color;
+import android.support.design.testutils.TabLayoutActions;
+import android.support.design.testutils.ViewPagerActions;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import org.junit.Test;
+
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import java.util.ArrayList;
+
+import static android.support.test.espresso.Espresso.onView;
+
+public class TabLayoutWithViewPagerTest
+ extends BaseInstrumentationTestCase<TabLayoutWithViewPagerActivity> {
+ private TabLayout mTabLayout;
+
+ private ViewPager mViewPager;
+
+ private ColorPagerAdapter mDefaultPagerAdapter;
+
+ protected static class BasePagerAdapter<Q> extends PagerAdapter {
+ protected ArrayList<Pair<String, Q>> mEntries = new ArrayList<>();
+
+ public void add(String title, Q content) {
+ mEntries.add(new Pair(title, content));
+ }
+
+ @Override
+ public int getCount() {
+ return mEntries.size();
+ }
+
+ protected void configureInstantiatedItem(View view, int position) {
+ switch (position) {
+ case 0:
+ view.setId(R.id.page_0);
+ break;
+ case 1:
+ view.setId(R.id.page_1);
+ break;
+ case 2:
+ view.setId(R.id.page_2);
+ break;
+ case 3:
+ view.setId(R.id.page_3);
+ break;
+ case 4:
+ view.setId(R.id.page_4);
+ break;
+ case 5:
+ view.setId(R.id.page_5);
+ break;
+ case 6:
+ view.setId(R.id.page_6);
+ break;
+ case 7:
+ view.setId(R.id.page_7);
+ break;
+ case 8:
+ view.setId(R.id.page_8);
+ break;
+ case 9:
+ view.setId(R.id.page_9);
+ break;
+ }
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ // The adapter is also responsible for removing the view.
+ container.removeView(((ViewHolder) object).view);
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ return ((ViewHolder) object).position;
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return ((ViewHolder) object).view == view;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ return mEntries.get(position).first;
+ }
+
+ protected static class ViewHolder {
+ final View view;
+ final int position;
+
+ public ViewHolder(View view, int position) {
+ this.view = view;
+ this.position = position;
+ }
+ }
+ }
+
+ protected static class ColorPagerAdapter extends BasePagerAdapter<Integer> {
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ final View view = new View(container.getContext());
+ view.setBackgroundColor(mEntries.get(position).second);
+ configureInstantiatedItem(view, position);
+
+ // Unlike ListView adapters, the ViewPager adapter is responsible
+ // for adding the view to the container.
+ container.addView(view);
+
+ return new ViewHolder(view, position);
+ }
+ }
+
+ protected static class TextPagerAdapter extends BasePagerAdapter<String> {
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ final TextView view = new TextView(container.getContext());
+ view.setText(mEntries.get(position).second);
+ configureInstantiatedItem(view, position);
+
+ // Unlike ListView adapters, the ViewPager adapter is responsible
+ // for adding the view to the container.
+ container.addView(view);
+
+ return new ViewHolder(view, position);
+ }
+ }
+
+ public TabLayoutWithViewPagerTest() {
+ super(TabLayoutWithViewPagerActivity.class);
+ }
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ final TabLayoutWithViewPagerActivity activity = getActivity();
+ mTabLayout = (TabLayout) activity.findViewById(R.id.tabs);
+ mViewPager = (ViewPager) activity.findViewById(R.id.tabs_viewpager);
+
+ mDefaultPagerAdapter = new ColorPagerAdapter();
+ mDefaultPagerAdapter.add("Red", Color.RED);
+ mDefaultPagerAdapter.add("Green", Color.GREEN);
+ mDefaultPagerAdapter.add("Blue", Color.BLUE);
+
+ // Configure view pager
+ onView(withId(R.id.tabs_viewpager)).perform(
+ ViewPagerActions.setAdapter(mDefaultPagerAdapter),
+ ViewPagerActions.scrollToPage(0));
+
+ // And wire the tab layout to it
+ onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+ }
+
+ /**
+ * Verifies that selecting pages in <code>ViewPager</code> also updates the tab selection
+ * in the wired <code>TabLayout</code>
+ */
+ private void verifyViewPagerSelection() {
+ int itemCount = mViewPager.getAdapter().getCount();
+
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollToPage(0));
+ assertEquals("Selected page", 0, mViewPager.getCurrentItem());
+ assertEquals("Selected tab", 0, mTabLayout.getSelectedTabPosition());
+
+ // Scroll tabs to the right
+ for (int i = 0; i < (itemCount - 1); i++) {
+ // Scroll one tab to the right
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollRight());
+ final int expectedCurrentTabIndex = i + 1;
+ assertEquals("Scroll right #" + i, expectedCurrentTabIndex,
+ mViewPager.getCurrentItem());
+ assertEquals("Selected tab after scrolling right #" + i, expectedCurrentTabIndex,
+ mTabLayout.getSelectedTabPosition());
+ }
+
+ // Scroll tabs to the left
+ for (int i = 0; i < (itemCount - 1); i++) {
+ // Scroll one tab to the left
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollLeft());
+ final int expectedCurrentTabIndex = itemCount - i - 2;
+ assertEquals("Scroll left #" + i, expectedCurrentTabIndex, mViewPager.getCurrentItem());
+ assertEquals("Selected tab after scrolling left #" + i, expectedCurrentTabIndex,
+ mTabLayout.getSelectedTabPosition());
+ }
+ }
+
+ /**
+ * Verifies that selecting pages in <code>ViewPager</code> also updates the tab selection
+ * in the wired <code>TabLayout</code>
+ */
+ private void verifyTabLayoutSelection() {
+ int itemCount = mTabLayout.getTabCount();
+
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollToPage(0));
+ assertEquals("Selected tab", 0, mTabLayout.getSelectedTabPosition());
+ assertEquals("Selected page", 0, mViewPager.getCurrentItem());
+
+ // Select tabs "going" to the right. Note that the first loop iteration tests the
+ // scenario of "selecting" the first tab when it's already selected.
+ for (int i = 0; i < itemCount; i++) {
+ onView(withId(R.id.tabs)).perform(TabLayoutActions.selectTab(i));
+ assertEquals("Selected tab after selecting #" + i, i,
+ mTabLayout.getSelectedTabPosition());
+ assertEquals("Select tab #" + i, i, mViewPager.getCurrentItem());
+ }
+
+ // Select tabs "going" to the left. Note that the first loop iteration tests the
+ // scenario of "selecting" the last tab when it's already selected.
+ for (int i = itemCount - 1; i >= 0; i--) {
+ onView(withId(R.id.tabs)).perform(TabLayoutActions.selectTab(i));
+ assertEquals("Scroll left #" + i, i, mViewPager.getCurrentItem());
+ assertEquals("Selected tab after scrolling left #" + i, i,
+ mTabLayout.getSelectedTabPosition());
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBasics() {
+ final int itemCount = mViewPager.getAdapter().getCount();
+
+ assertEquals("Matching item count", itemCount, mTabLayout.getTabCount());
+
+ for (int i = 0; i < itemCount; i++) {
+ assertEquals("Tab #" +i, mViewPager.getAdapter().getPageTitle(i),
+ mTabLayout.getTabAt(i).getText());
+ }
+
+ assertEquals("Selected tab", mViewPager.getCurrentItem(),
+ mTabLayout.getSelectedTabPosition());
+
+ verifyViewPagerSelection();
+ }
+
+ @Test
+ @SmallTest
+ public void testInteraction() {
+ assertEquals("Default selected page", 0, mViewPager.getCurrentItem());
+ assertEquals("Default selected tab", 0, mTabLayout.getSelectedTabPosition());
+
+ verifyTabLayoutSelection();
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterContentChange() {
+ // Verify that we have the expected initial adapter
+ PagerAdapter initialAdapter = mViewPager.getAdapter();
+ assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass());
+ assertEquals("Initial adapter page count", 3, initialAdapter.getCount());
+
+ // Add two more entries to our adapter
+ final int newItemCount = 5;
+ mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+ mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.notifyAdapterContentChange());
+
+ // We have more comprehensive test coverage for changing the ViewPager adapter in v4/tests.
+ // Here we are focused on testing the continuous integration of TabLayout with the new
+ // content of ViewPager
+
+ assertEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+
+ for (int i = 0; i < newItemCount; i++) {
+ assertEquals("Tab #" +i, mViewPager.getAdapter().getPageTitle(i),
+ mTabLayout.getTabAt(i).getText());
+ }
+
+ verifyViewPagerSelection();
+ verifyTabLayoutSelection();
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterChange() {
+ // Verify that we have the expected initial adapter
+ PagerAdapter initialAdapter = mViewPager.getAdapter();
+ assertEquals("Initial adapter class", ColorPagerAdapter.class, initialAdapter.getClass());
+ assertEquals("Initial adapter page count", 3, initialAdapter.getCount());
+
+ // Create a new adapter
+ TextPagerAdapter newAdapter = new TextPagerAdapter();
+ final int newItemCount = 6;
+ for (int i = 0; i < newItemCount; i++) {
+ newAdapter.add("Title " + i, "Body " + i);
+ }
+ // And set it on the ViewPager
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.setAdapter(newAdapter),
+ ViewPagerActions.scrollToPage(0));
+
+ // As TabLayout doesn't track adapter changes, we need to re-wire the new adapter
+ onView(withId(R.id.tabs)).perform(TabLayoutActions.setupWithViewPager(mViewPager));
+
+ // We have more comprehensive test coverage for changing the ViewPager adapter in v4/tests.
+ // Here we are focused on testing the integration of TabLayout with the new
+ // content of ViewPager
+
+ assertEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+
+ for (int i = 0; i < newItemCount; i++) {
+ assertEquals("Tab #" +i, mViewPager.getAdapter().getPageTitle(i),
+ mTabLayout.getTabAt(i).getText());
+ }
+
+ verifyViewPagerSelection();
+ verifyTabLayoutSelection();
+ }
+}
diff --git a/graphics/drawable/Android.mk b/graphics/drawable/Android.mk
index bc45e10..563ad30 100644
--- a/graphics/drawable/Android.mk
+++ b/graphics/drawable/Android.mk
@@ -30,9 +30,7 @@
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under, animated/src)
-LOCAL_JAVA_LIBRARIES := android-support-v4
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-vectordrawable
+LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-vectordrawable
LOCAL_AAPT_FLAGS := --no-version-vectors
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
index c35a704..f56f612 100644
--- a/graphics/drawable/animated/build.gradle
+++ b/graphics/drawable/animated/build.gradle
@@ -75,17 +75,6 @@
artifacts.add('archives', sourcesJarTask);
}
-// TODO make this generic for all projects
-afterEvaluate {
- def originalTask = tasks['packageDebugAndroidTest']
- tasks['assembleDebugAndroidTest'].doLast {
- copy {
- from(originalTask.outputFile)
- into(rootProject.ext.testApkDistOut)
- }
- }
-}
-
uploadArchives {
repositories {
mavenDeployer {
@@ -118,4 +107,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/graphics/drawable/animated/tests/NO_DOCS b/graphics/drawable/animated/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/graphics/drawable/animated/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
index 235e5b7..bf271b4 100644
--- a/graphics/drawable/static/build.gradle
+++ b/graphics/drawable/static/build.gradle
@@ -75,17 +75,6 @@
artifacts.add('archives', sourcesJarTask);
}
-// TODO make this generic for all projects
-afterEvaluate {
- def originalTask = tasks['packageDebugAndroidTest']
- tasks['assembleDebugAndroidTest'].doLast {
- copy {
- from(originalTask.outputFile)
- into(rootProject.ext.testApkDistOut)
- }
- }
-}
-
uploadArchives {
repositories {
mavenDeployer {
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
index 9652e39..064d040 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java
@@ -14,6 +14,9 @@
package android.support.graphics.drawable;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -44,9 +47,6 @@
import android.util.Log;
import android.util.Xml;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
import java.io.IOException;
import java.util.ArrayList;
import java.util.Stack;
@@ -533,10 +533,7 @@
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
-
- final VectorDrawableCompat drawable = new VectorDrawableCompat();
- drawable.inflate(res, parser, attrs, theme);
- return drawable;
+ return createFromXmlInner(res, parser, attrs, theme);
} catch (XmlPullParserException e) {
Log.e(LOGTAG, "parser error", e);
} catch (IOException e) {
@@ -545,6 +542,19 @@
return null;
}
+ /**
+ * Create a VectorDrawableCompat from inside an XML document using an optional
+ * {@link Theme}. Called on a parser positioned at a tag in an XML
+ * document, tries to create a Drawable from that tag. Returns {@code null}
+ * if the tag is not a valid drawable.
+ */
+ public static VectorDrawableCompat createFromXmlInner(Resources r, XmlPullParser parser,
+ AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException {
+ final VectorDrawableCompat drawable = new VectorDrawableCompat();
+ drawable.inflate(r, parser, attrs, theme);
+ return drawable;
+ }
+
private static int applyAlpha(int color, float alpha) {
int alphaBytes = Color.alpha(color);
color &= 0x00FFFFFF;
diff --git a/graphics/drawable/static/tests/NO_DOCS b/graphics/drawable/static/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/graphics/drawable/static/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
index 2dab79a..4163eb2 100644
--- a/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
+++ b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
@@ -26,6 +26,7 @@
import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.graphics.drawable.test.R;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import org.xmlpull.v1.XmlPullParserException;
@@ -33,6 +34,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
+@MediumTest
public class VectorDrawableTest extends AndroidTestCase {
private static final String LOGTAG = "VectorDrawableTest";
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index 567a7d7..9d4d9f7 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -33,10 +33,10 @@
<string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Ignorer l\'élément suivant"</string>
<string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Ignorer l\'élément précédent"</string>
<string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Autres actions"</string>
- <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Désélectionner \"J\'aime\""</string>
- <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Sélectionner \"J\'aime\""</string>
- <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Désélectionner \"Je n\'aime pas\""</string>
- <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Sélectionner \"Je n\'aime pas\""</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Annuler +1"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Sélectionner +1"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Annuler -1"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Sélectionner -1"</string>
<string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ne rien lire en boucle"</string>
<string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Tout lire en boucle"</string>
<string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Lire en boucle un élément"</string>
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
index dccde67..e0fad0a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -381,14 +381,6 @@
private boolean mKeyPressed = false;
- private void playSound(View v, int soundEffect) {
- if (v.isSoundEffectsEnabled()) {
- Context ctx = v.getContext();
- AudioManager manager = (AudioManager)ctx.getSystemService(Context.AUDIO_SERVICE);
- manager.playSoundEffect(soundEffect);
- }
- }
-
/**
* Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
*/
@@ -424,7 +416,6 @@
}
if (!mKeyPressed) {
mKeyPressed = true;
- playSound(v, AudioManager.FX_KEY_CLICK);
mStylist.onAnimateItemPressed(avh, mKeyPressed);
}
break;
diff --git a/v17/tests/NO_DOCS b/v17/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v17/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/v4/build.gradle b/v4/build.gradle
index dce3a15..5abdbb9 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -150,17 +150,6 @@
artifacts.add('archives', sourcesJarTask);
}
-// TODO make this generic for all projects
-afterEvaluate {
- def originalTask = tasks['packageDebugAndroidTest']
- tasks['assembleDebugAndroidTest'].doLast {
- copy {
- from(originalTask.outputFile)
- into(rootProject.ext.testApkDistOut)
- }
- }
-}
-
uploadArchives {
repositories {
mavenDeployer {
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index dcce7f5..f60d596 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -200,6 +200,9 @@
mImpl.getItem(mediaId, cb);
}
+ /**
+ * A class with information on a single media item for use in browsing media.
+ */
public static class MediaItem implements Parcelable {
private final int mFlags;
private final MediaDescriptionCompat mDescription;
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 205e789..90a361e 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -794,7 +794,7 @@
}
/**
- * Gets any extras about the brwoser service.
+ * Gets any extras about the browser service.
*/
public Bundle getExtras() {
return mExtras;
diff --git a/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
index 2c58ad5..27f277b 100644
--- a/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
+++ b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
@@ -22,8 +22,10 @@
import android.support.v4.test.R;
import android.support.v4.view.ViewCompat;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.View;
+@MediumTest
public class FragmentTransitionTest extends
ActivityInstrumentationTestCase2<FragmentTestActivity> {
private TestFragment mStartFragment;
diff --git a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
index 6296903..b25ed4f 100644
--- a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
+++ b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
@@ -18,6 +18,7 @@
import android.graphics.Color;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import java.lang.Integer;
import java.util.ArrayList;
@@ -25,6 +26,7 @@
/**
* @hide
*/
+@SmallTest
public class ColorUtilsTest extends AndroidTestCase {
// 0.5% of the max value
diff --git a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
index ab32dc5..1d8ef7a 100644
--- a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
+++ b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
@@ -17,10 +17,12 @@
package android.support.v4.text;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import java.util.Locale;
/** @hide */
+@SmallTest
public class BidiFormatterTest extends AndroidTestCase {
private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
index fc571a8..2b83fc2 100644
--- a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -17,6 +17,7 @@
import android.content.Context;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -29,6 +30,7 @@
/**
* @hide
*/
+@MediumTest
abstract public class ScrollerCompatTestBase extends AndroidTestCase {
private static final boolean DEBUG = false;
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index baa32eb..0eb6a7b 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -23,7 +23,10 @@
LOCAL_SDK_VERSION := current
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_JAVA_LIBRARIES += android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-vectordrawable
+LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_AAPT_FLAGS += --auto-add-overlay \
+ --no-version-vectors
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index ff8c7d8..1bfb318 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -500,6 +500,7 @@
field public static int spinnerDropDownItemStyle;
field public static int spinnerStyle;
field public static int splitTrack;
+ field public static int srcCompat;
field public static int state_above_anchor;
field public static int submitBackground;
field public static int subtitle;
@@ -719,13 +720,13 @@
field public static int abc_action_bar_item_background_material;
field public static int abc_btn_borderless_material;
field public static int abc_btn_check_material;
- field public static int abc_btn_check_to_on_mtrl_000;
- field public static int abc_btn_check_to_on_mtrl_015;
+ field public static int abc_btn_checkbox_checked_mtrl;
+ field public static int abc_btn_checkbox_unchecked_mtrl;
field public static int abc_btn_colored_material;
field public static int abc_btn_default_mtrl_shape;
field public static int abc_btn_radio_material;
- field public static int abc_btn_radio_to_on_mtrl_000;
- field public static int abc_btn_radio_to_on_mtrl_015;
+ field public static int abc_btn_radio_off_mtrl;
+ field public static int abc_btn_radio_on_mtrl;
field public static int abc_btn_rating_star_off_mtrl_alpha;
field public static int abc_btn_rating_star_on_mtrl_alpha;
field public static int abc_btn_switch_to_on_mtrl_00001;
@@ -737,22 +738,22 @@
field public static int abc_dialog_material_background_dark;
field public static int abc_dialog_material_background_light;
field public static int abc_edit_text_material;
- field public static int abc_ic_ab_back_mtrl_am_alpha;
- field public static int abc_ic_clear_mtrl_alpha;
+ field public static int abc_ic_ab_back_material;
+ field public static int abc_ic_clear_material;
field public static int abc_ic_commit_search_api_mtrl_alpha;
- field public static int abc_ic_go_search_api_mtrl_alpha;
- field public static int abc_ic_menu_copy_mtrl_am_alpha;
- field public static int abc_ic_menu_cut_mtrl_alpha;
- field public static int abc_ic_menu_moreoverflow_mtrl_alpha;
- field public static int abc_ic_menu_paste_mtrl_am_alpha;
- field public static int abc_ic_menu_selectall_mtrl_alpha;
- field public static int abc_ic_menu_share_mtrl_alpha;
+ field public static int abc_ic_go_search_api_material;
+ field public static int abc_ic_menu_copy_material;
+ field public static int abc_ic_menu_cut_material;
+ field public static int abc_ic_menu_overflow_material;
+ field public static int abc_ic_menu_paste_material;
+ field public static int abc_ic_menu_selectall_material;
+ field public static int abc_ic_menu_share_material;
field public static int abc_ic_search_api_mtrl_alpha;
field public static int abc_ic_star_black_16dp;
field public static int abc_ic_star_black_36dp;
field public static int abc_ic_star_half_black_16dp;
field public static int abc_ic_star_half_black_36dp;
- field public static int abc_ic_voice_search_api_mtrl_alpha;
+ field public static int abc_ic_voice_search_api_material;
field public static int abc_item_background_holo_dark;
field public static int abc_item_background_holo_light;
field public static int abc_list_divider_mtrl_alpha;
@@ -1338,6 +1339,9 @@
field public static int AlertDialog_listLayout;
field public static int AlertDialog_multiChoiceItemLayout;
field public static int AlertDialog_singleChoiceItemLayout;
+ field public static final int[] AppCompatImageView;
+ field public static int AppCompatImageView_android_src;
+ field public static int AppCompatImageView_srcCompat;
field public static final int[] AppCompatTextView;
field public static int AppCompatTextView_android_textAppearance;
field public static int AppCompatTextView_textAllCaps;
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 19377ea..fe55fee 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -4,6 +4,7 @@
dependencies {
compile project(':support-v4')
+ compile project(':support-vector-drawable')
androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
testCompile 'junit:junit:4.12'
@@ -35,6 +36,10 @@
targetCompatibility JavaVersion.VERSION_1_7
}
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
diff --git a/v7/appcompat/res-public/values/public_attrs.xml b/v7/appcompat/res-public/values/public_attrs.xml
index 8454ba8..fdb7ff5 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -147,6 +147,7 @@
<public type="attr" name="ratingBarStyle"/>
<public type="attr" name="ratingBarStyleIndicator"/>
<public type="attr" name="ratingBarStyleSmall"/>
+ <public type="attr" name="srcCompat"/>
<public type="attr" name="searchHintIcon"/>
<public type="attr" name="searchIcon"/>
<public type="attr" name="searchViewStyle"/>
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 9911008..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 69ff9dd..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 9218981..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index a588576..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 46ccacc..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index ce64334..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index f39c153..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 706fc1f..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index e78bcaf..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index 00e189b..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 8610c50..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index e631df7..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
deleted file mode 100644
index cd1f57c..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index a6dbc2a..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 57211b5..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index a262b0c..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index d8eaf07..0000000
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index b872414..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 254f806..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index efe4446..0000000
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index ffa1654..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 88e34c4..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 87bf8d3..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 33b4f0f..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index fb54215..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 3cdb6cf..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index a91425a..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index fb91811..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 90fe333..0000000
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 7a9fcbc..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 3b052e5..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 96a8693..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 6e18d40..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 90fbc56..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index dde307e..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index 0084c12..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 559b835..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 44a3768..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index f95f7f7..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 1492ab6..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 7c011af..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
deleted file mode 100644
index 36f664c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index a216da1..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index 4902520..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 59a683a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 03bf49c..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 342323b..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 154babd..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index 6d90d0c..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index 19e000a..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 6448549..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index cd38901..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index e76c83e..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 9aabc43..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 6a7161f..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
deleted file mode 100644
index 6be7e09..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 209898d..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index 7329c15..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index accf80e..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 8c82ec3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 8fc0a9b..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index 3038d70..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index f99802f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index b85e87f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
deleted file mode 100644
index d041623..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_go_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 90d6ba3..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 63e541f..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index c382aa6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index f71485c..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 162ab98..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
deleted file mode 100644
index d95a377..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index dacf407..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
deleted file mode 100644
index d44bbae..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
deleted file mode 100644
index 4e18de2..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_check_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
deleted file mode 100644
index 5fa3266..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_000.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
deleted file mode 100644
index c11cb2e..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_radio_to_on_mtrl_015.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
deleted file mode 100644
index 41d6d6b..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_ab_back_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
deleted file mode 100644
index da2b577..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_clear_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
deleted file mode 100644
index 715db8a..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_copy_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
deleted file mode 100644
index 397fd91..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_cut_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
deleted file mode 100644
index 16e9e14..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_moreoverflow_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
deleted file mode 100644
index 1891b3d..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_paste_mtrl_am_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
deleted file mode 100644
index 591a1c9..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_selectall_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
deleted file mode 100644
index ba16aac..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_menu_share_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
deleted file mode 100644
index d191642..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_voice_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable/abc_btn_check_material.xml b/v7/appcompat/res/drawable/abc_btn_check_material.xml
index 4934a92..f8e90a9 100644
--- a/v7/appcompat/res/drawable/abc_btn_check_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_check_material.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2015 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.
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:drawable="@drawable/abc_btn_check_to_on_mtrl_015" />
- <item android:drawable="@drawable/abc_btn_check_to_on_mtrl_000" />
+ <item android:state_checked="true" android:drawable="@drawable/abc_btn_checkbox_checked_mtrl" />
+ <item android:drawable="@drawable/abc_btn_checkbox_unchecked_mtrl" />
</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml
new file mode 100644
index 0000000..638339a
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_checkbox_checked_mtrl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="abc_btn_checkbox_checked"
+ android:width="32dp"
+ android:viewportWidth="48"
+ android:height="32dp"
+ android:viewportHeight="48">
+ <group
+ android:name="icon_null"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.2"
+ android:scaleY="0.2">
+ <group
+ android:name="check"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="check_path_merged"
+ android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -5.0,-5.00001525879 -5.0,-5.00001525879 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 3.58590698242,3.58601379395 3.58590698242,3.58601379395 c 0.0,0.0 7.58590698242,-7.58601379395 7.58590698242,-7.58601379395 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -9.0,9.00001525879 -9.0,9.00001525879 Z"
+ android:fillColor="#FF000000"/>
+ </group>
+ <group
+ android:name="box_dilate"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_inner_merged"
+ android:pathData="M 0.0,-1.0 l 0.0,0.0 c 0.5522847498,0.0 1.0,0.4477152502 1.0,1.0 l 0.0,0.0 c 0.0,0.5522847498 -0.4477152502,1.0 -1.0,1.0 l 0.0,0.0 c -0.5522847498,0.0 -1.0,-0.4477152502 -1.0,-1.0 l 0.0,0.0 c 0.0,-0.5522847498 0.4477152502,-1.0 1.0,-1.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:fillColor="#FF000000"
+ android:fillAlpha="0"/>
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml
new file mode 100644
index 0000000..0301f1f
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_checkbox_unchecked_mtrl.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="abc_btn_checkbox_unchecked"
+ android:width="32dp"
+ android:viewportWidth="48"
+ android:height="32dp"
+ android:viewportHeight="48">
+ <group
+ android:name="icon_null"
+ android:translateX="24"
+ android:translateY="24"
+ android:scaleX="0.2"
+ android:scaleY="0.2">
+ <group
+ android:name="check"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_outer_merged"
+ android:pathData="M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M -2.0,5.00001525879 c 0.0,0.0 -1.4234161377,-1.40159606934 -1.4234161377,-1.40159606934 c 0.0,0.0 1.41409301758,-1.41409301758 1.41409301758,-1.41409301758 c 0.0,0.0 0.00932312011719,-0.0124053955078 0.00932312011719,-0.0124053955078 c 0.0,0.0 0.0234069824219,-0.0235137939453 0.0234069824219,-0.0235137939453 c 0.0,0.0 1.41409301758,1.41409301758 1.41409301758,1.41409301758 c 0.0,0.0 -1.4375,1.43751525879 -1.4375,1.43751525879 Z"
+ android:fillColor="#FF000000"
+ android:fillAlpha="0"/>
+ </group>
+ <group
+ android:name="box_dilate"
+ android:scaleX="7.5"
+ android:scaleY="7.5">
+ <path
+ android:name="box_inner_merged"
+ android:pathData="M -7.0,-7.0 l 14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l -14.0,0.0 c 0.0,0.0 0.0,0.0 0.0,0.0 l 0.0,-14.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z M 7.0,-9.0 c 0.0,0.0 -14.0,0.0 -14.0,0.0 c -1.1044921875,0.0 -2.0,0.8955078125 -2.0,2.0 c 0.0,0.0 0.0,14.0 0.0,14.0 c 0.0,1.1044921875 0.8955078125,2.0 2.0,2.0 c 0.0,0.0 14.0,0.0 14.0,0.0 c 1.1044921875,0.0 2.0,-0.8955078125 2.0,-2.0 c 0.0,0.0 0.0,-14.0 0.0,-14.0 c 0.0,-1.1044921875 -0.8955078125,-2.0 -2.0,-2.0 c 0.0,0.0 0.0,0.0 0.0,0.0 Z"
+ android:fillColor="#FF000000"/>
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_material.xml b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
index 6e9f9cf..ac4eead 100644
--- a/v7/appcompat/res/drawable/abc_btn_radio_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
@@ -15,6 +15,6 @@
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_to_on_mtrl_015" />
- <item android:drawable="@drawable/abc_btn_radio_to_on_mtrl_000" />
+ <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_on_mtrl" />
+ <item android:drawable="@drawable/abc_btn_radio_off_mtrl" />
</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml
new file mode 100644
index 0000000..9c4f3b1
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_radio_off_mtrl.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="btn_radio_to_on_mtrl"
+ android:width="32dp"
+ android:viewportWidth="32"
+ android:height="32dp"
+ android:viewportHeight="32">
+ <group
+ android:name="btn_radio_to_on_mtrl_0"
+ android:translateX="16"
+ android:translateY="16" >
+ <group
+ android:name="ring_outer" >
+ <path
+ android:name="ring_outer_path"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="2"
+ android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+ </group>
+ <group
+ android:name="dot_group"
+ android:scaleX="0"
+ android:scaleY="0" >
+ <path
+ android:name="dot_path"
+ android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+ android:fillColor="#FF000000" />
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml b/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml
new file mode 100644
index 0000000..ab56e58
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_btn_radio_on_mtrl.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:name="btn_radio_to_off_mtrl"
+ android:width="32dp"
+ android:viewportWidth="32"
+ android:height="32dp"
+ android:viewportHeight="32">
+ <group
+ android:name="btn_radio_to_off_mtrl_0"
+ android:translateX="16"
+ android:translateY="16" >
+ <group
+ android:name="ring_outer" >
+ <path
+ android:name="ring_outer_path"
+ android:strokeColor="#FF000000"
+ android:strokeWidth="2"
+ android:pathData="M 0.0,-9.0 c 4.9705627482,0.0 9.0,4.0294372518 9.0,9.0 c 0.0,4.9705627482 -4.0294372518,9.0 -9.0,9.0 c -4.9705627482,0.0 -9.0,-4.0294372518 -9.0,-9.0 c 0.0,-4.9705627482 4.0294372518,-9.0 9.0,-9.0 Z" />
+ </group>
+ <group
+ android:name="dot_group" >
+ <path
+ android:name="dot_path"
+ android:pathData="M 0.0,-5.0 c -2.7619934082,0.0 -5.0,2.2380065918 -5.0,5.0 c 0.0,2.7619934082 2.2380065918,5.0 5.0,5.0 c 2.7619934082,0.0 5.0,-2.2380065918 5.0,-5.0 c 0.0,-2.7619934082 -2.2380065918,-5.0 -5.0,-5.0 Z"
+ android:fillColor="#FF000000" />
+ </group>
+ </group>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml b/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml
new file mode 100644
index 0000000..5a89523
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_ab_back_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M20,11L7.8,11l5.6,-5.6L12,4l-8,8l8,8l1.4,-1.4L7.8,13L20,13L20,11z"
+ android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_clear_material.xml b/v7/appcompat/res/drawable/abc_ic_clear_material.xml
new file mode 100644
index 0000000..e6d106b
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_clear_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M19,6.41L17.59,5,12,10.59,6.41,5,5,6.41,10.59,12,5,17.59,6.41,19,12,13.41,17.59,19,19,17.59,13.41,12z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml
new file mode 100644
index 0000000..0c88119
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_go_search_api_material.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M10,6l-1.4,1.4 4.599999,4.6 -4.599999,4.6 1.4,1.4 6,-6z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml
new file mode 100644
index 0000000..6af0e0c
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_copy_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M16,1L4,1C2.9,1 2,1.9 2,3l0,14l2,0L4,3l12,0L16,1zM19,5L8,5C6.9,5 6,5.9 6,7l0,14c0,1.1 0.9,2 2,2l11,0c1.1,0 2,-0.9 2,-2L21,7C21,5.9 20.1,5 19,5zM19,21L8,21L8,7l11,0L19,21z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml
new file mode 100644
index 0000000..22cb81d
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_cut_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M10,6c0,-2.2 -1.8,-4 -4,-4S2,3.8 2,6c0,2.2 1.8,4 4,4c0.6,0 1.1,-0.1 1.6,-0.4L10,12l-2.4,2.4C7.1,14.1 6.6,14 6,14c-2.2,0 -4,1.8 -4,4c0,2.2 1.8,4 4,4s4,-1.8 4,-4c0,-0.6 -0.1,-1.1 -0.4,-1.6L12,14l7,7l4,0L9.6,7.6C9.9,7.1 10,6.6 10,6zM6,8C4.9,8 4,7.1 4,6s0.9,-2 2,-2c1.1,0 2,0.9 2,2S7.1,8 6,8zM6,20c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2c1.1,0 2,0.9 2,2S7.1,20 6,20zM12,11.5c0.3,0 0.5,0.2 0.5,0.5c0,0.3 -0.2,0.5 -0.5,0.5c-0.3,0 -0.5,-0.2 -0.5,-0.5C11.5,11.7 11.7,11.5 12,11.5zM23,3l-4,0l-6,6l2,2L23,3z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml
new file mode 100644
index 0000000..1420edd
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_overflow_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2c-1.1,0 -2,0.9 -2,2S10.9,8 12,8zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,10 12,10zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2c1.1,0 2,-0.9 2,-2S13.1,16 12,16z"
+ android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml
new file mode 100644
index 0000000..b642aae
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_paste_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M19,2l-4.2,0c-0.4,-1.2 -1.5,-2 -2.8,-2c-1.3,0 -2.4,0.8 -2.8,2L5,2C3.9,2 3,2.9 3,4l0,16c0,1.1 0.9,2 2,2l14,0c1.1,0 2,-0.9 2,-2L21,4C21,2.9 20.1,2 19,2zM12,2c0.6,0 1,0.4 1,1s-0.4,1 -1,1c-0.6,0 -1,-0.4 -1,-1S11.4,2 12,2zM19,20L5,20L5,4l2,0l0,3l10,0L17,4l2,0L19,20z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml
new file mode 100644
index 0000000..cd0bbd5
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_selectall_material.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:autoMirrored="true"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M3,5l2,0L5,3C3.9,3 3,3.9 3,5zM3,13l2,0l0,-2L3,11L3,13zM7,21l2,0l0,-2L7,19L7,21zM3,9l2,0L5,7L3,7L3,9zM13,3l-2,0l0,2l2,0L13,3zM19,3l0,2l2,0C21,3.9 20.1,3 19,3zM5,21l0,-2L3,19C3,20.1 3.9,21 5,21zM3,17l2,0l0,-2L3,15L3,17zM9,3L7,3l0,2l2,0L9,3zM11,21l2,0l0,-2l-2,0L11,21zM19,13l2,0l0,-2l-2,0L19,13zM19,21c1.1,0 2,-0.9 2,-2l-2,0L19,21zM19,9l2,0L21,7l-2,0L19,9zM19,17l2,0l0,-2l-2,0L19,17zM15,21l2,0l0,-2l-2,0L15,21zM15,5l2,0L17,3l-2,0L15,5zM7,17l10,0L17,7L7,7L7,17zM9,9l6,0l0,6L9,15L9,9z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml b/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml
new file mode 100644
index 0000000..ce5ceff
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_menu_share_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M18,16.1c-0.8,0 -1.5,0.3 -2,0.8l-7.1,-4.2C9,12.5 9,12.2 9,12s0,-0.5 -0.1,-0.7L16,7.2C16.5,7.7 17.200001,8 18,8c1.7,0 3,-1.3 3,-3s-1.3,-3 -3,-3s-3,1.3 -3,3c0,0.2 0,0.5 0.1,0.7L8,9.8C7.5,9.3 6.8,9 6,9c-1.7,0 -2.9,1.2 -2.9,2.9s1.3,3 3,3c0.8,0 1.5,-0.3 2,-0.8l7.1,4.2c-0.1,0.3 -0.1,0.5 -0.1,0.7c0,1.6 1.3,2.9 2.9,2.9s2.9,-1.3 2.9,-2.9S19.6,16.1 18,16.1z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml
new file mode 100644
index 0000000..143db55
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_voice_search_api_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:pathData="M12,14c1.7,0 3,-1.3 3,-3l0,-6c0,-1.7 -1.3,-3 -3,-3c-1.7,0 -3,1.3 -3,3l0,6C9,12.7 10.3,14 12,14zM17.299999,11c0,3 -2.5,5.1 -5.3,5.1c-2.8,0 -5.3,-2.1 -5.3,-5.1L5,11c0,3.4 2.7,6.2 6,6.7L11,21l2,0l0,-3.3c3.3,-0.5 6,-3.3 6,-6.7L17.299999,11.000001z"
+ android:fillColor="@android:color/white"/>
+</vector>
diff --git a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
index 2944d98..b3babb2 100644
--- a/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
+++ b/v7/appcompat/res/layout/abc_action_mode_close_item_material.xml
@@ -16,11 +16,12 @@
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/action_mode_close_button"
android:contentDescription="@string/abc_action_mode_done"
android:focusable="true"
android:clickable="true"
- android:src="?attr/actionModeCloseDrawable"
+ app:srcCompat="?attr/actionModeCloseDrawable"
style="?attr/actionModeCloseButtonStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_activity_chooser_view.xml b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
index 2522f1a..0100c23 100644
--- a/v7/appcompat/res/layout/abc_activity_chooser_view.xml
+++ b/v7/appcompat/res/layout/abc_activity_chooser_view.xml
@@ -31,16 +31,16 @@
android:layout_gravity="center"
android:focusable="true"
android:addStatesFromChildren="true"
- android:background="?attr/actionBarItemBackground">
+ android:background="?attr/actionBarItemBackground"
+ android:paddingTop="2dip"
+ android:paddingBottom="2dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip">
<ImageView android:id="@+id/image"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center"
- android:layout_marginTop="2dip"
- android:layout_marginBottom="2dip"
- android:layout_marginLeft="12dip"
- android:layout_marginRight="12dip"
android:scaleType="fitCenter"
android:adjustViewBounds="true" />
@@ -53,16 +53,16 @@
android:layout_gravity="center"
android:focusable="true"
android:addStatesFromChildren="true"
- android:background="?attr/actionBarItemBackground">
+ android:background="?attr/actionBarItemBackground"
+ android:paddingTop="2dip"
+ android:paddingBottom="2dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip">
<ImageView android:id="@+id/image"
android:layout_width="32dip"
android:layout_height="32dip"
android:layout_gravity="center"
- android:layout_marginTop="2dip"
- android:layout_marginBottom="2dip"
- android:layout_marginLeft="12dip"
- android:layout_marginRight="12dip"
android:scaleType="fitCenter"
android:adjustViewBounds="true" />
diff --git a/v7/appcompat/res/layout/notification_media_cancel_action.xml b/v7/appcompat/res/layout/notification_media_cancel_action.xml
index e31d891..39a316c 100644
--- a/v7/appcompat/res/layout/notification_media_cancel_action.xml
+++ b/v7/appcompat/res/layout/notification_media_cancel_action.xml
@@ -16,6 +16,7 @@
-->
<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
style="?android:attr/borderlessButtonStyle"
android:id="@+id/cancel_action"
android:layout_width="48dp"
@@ -23,6 +24,6 @@
android:layout_marginLeft="2dp"
android:layout_marginRight="2dp"
android:layout_weight="1"
- android:src="@drawable/abc_ic_clear_mtrl_alpha"
+ app:srcCompat="@drawable/abc_ic_clear_material"
android:gravity="center"
android:visibility="gone"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 66b8efa..f3e496e 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -965,4 +965,10 @@
<attr name="allowStacking" format="boolean" />
</declare-styleable>
+ <declare-styleable name="AppCompatImageView">
+ <attr name="android:src"/>
+ <!-- TODO -->
+ <attr name="srcCompat" format="reference" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index dad7570..9063b1e 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -75,7 +75,7 @@
</style>
<style name="Base.Widget.AppCompat.ActionButton.Overflow" parent="RtlUnderlay.Widget.AppCompat.ActionButton.Overflow">
- <item name="android:src">@drawable/abc_ic_menu_moreoverflow_mtrl_alpha</item>
+ <item name="srcCompat">@drawable/abc_ic_menu_overflow_material</item>
<item name="android:background">?attr/actionBarItemBackground</item>
<item name="android:contentDescription">@string/abc_action_menu_overflow_description</item>
<item name="android:minWidth">@dimen/abc_action_button_min_width_overflow_material</item>
@@ -327,11 +327,11 @@
<item name="layout">@layout/abc_search_view</item>
<item name="queryBackground">@drawable/abc_textfield_search_material</item>
<item name="submitBackground">@drawable/abc_textfield_search_material</item>
- <item name="closeIcon">@drawable/abc_ic_clear_mtrl_alpha</item>
+ <item name="closeIcon">@drawable/abc_ic_clear_material</item>
<item name="searchIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
<item name="searchHintIcon">@drawable/abc_ic_search_api_mtrl_alpha</item>
- <item name="goIcon">@drawable/abc_ic_go_search_api_mtrl_alpha</item>
- <item name="voiceIcon">@drawable/abc_ic_voice_search_api_mtrl_alpha</item>
+ <item name="goIcon">@drawable/abc_ic_go_search_api_material</item>
+ <item name="voiceIcon">@drawable/abc_ic_voice_search_api_material</item>
<item name="commitIcon">@drawable/abc_ic_commit_search_api_mtrl_alpha</item>
<item name="suggestionRowLayout">@layout/abc_search_dropdown_item_icons_2line</item>
</style>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index 38757df..4aaaed1 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -123,7 +123,7 @@
<item name="selectableItemBackground">@drawable/abc_item_background_holo_dark</item>
<item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
<item name="borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
- <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+ <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material</item>
<item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
<item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
@@ -152,14 +152,14 @@
<item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
<item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
<item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
- <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+ <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_material</item>
<item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
- <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
- <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
- <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
- <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
- <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
+ <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_material</item>
+ <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_material</item>
+ <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_material</item>
+ <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_material</item>
+ <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material</item>
<!-- Panel attributes -->
<item name="panelMenuListWidth">@dimen/abc_panel_menu_list_width</item>
@@ -280,7 +280,7 @@
<item name="selectableItemBackground">@drawable/abc_item_background_holo_light</item>
<item name="selectableItemBackgroundBorderless">?attr/selectableItemBackground</item>
<item name="borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
- <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+ <item name="homeAsUpIndicator">@drawable/abc_ic_ab_back_material</item>
<item name="dividerVertical">@drawable/abc_list_divider_mtrl_alpha</item>
<item name="dividerHorizontal">@drawable/abc_list_divider_mtrl_alpha</item>
@@ -306,14 +306,14 @@
<item name="actionModeStyle">@style/Widget.AppCompat.ActionMode</item>
<item name="actionModeBackground">@drawable/abc_cab_background_top_material</item>
<item name="actionModeSplitBackground">?attr/colorPrimaryDark</item>
- <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_mtrl_am_alpha</item>
+ <item name="actionModeCloseDrawable">@drawable/abc_ic_ab_back_material</item>
<item name="actionModeCloseButtonStyle">@style/Widget.AppCompat.ActionButton.CloseMode</item>
- <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_mtrl_alpha</item>
- <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_mtrl_am_alpha</item>
- <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_mtrl_am_alpha</item>
- <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_mtrl_alpha</item>
- <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_mtrl_alpha</item>
+ <item name="actionModeCutDrawable">@drawable/abc_ic_menu_cut_material</item>
+ <item name="actionModeCopyDrawable">@drawable/abc_ic_menu_copy_material</item>
+ <item name="actionModePasteDrawable">@drawable/abc_ic_menu_paste_material</item>
+ <item name="actionModeSelectAllDrawable">@drawable/abc_ic_menu_selectall_material</item>
+ <item name="actionModeShareDrawable">@drawable/abc_ic_menu_share_material</item>
<!-- Dropdown Spinner Attributes -->
<item name="actionDropDownStyle">@style/Widget.AppCompat.Light.Spinner.DropDown.ActionBar</item>
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java b/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
index 15dacb0..1b06b82 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatButton.java
@@ -59,7 +59,7 @@
}
public AppCompatButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mDrawableManager = AppCompatDrawableManager.get();
mBackgroundTintHelper = new AppCompatBackgroundHelper(this, mDrawableManager);
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatCheckBox.java b/v7/appcompat/src/android/support/v7/widget/AppCompatCheckBox.java
index 4a6ecbe..65cab42 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatCheckBox.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatCheckBox.java
@@ -55,7 +55,7 @@
}
public AppCompatCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mDrawableManager = AppCompatDrawableManager.get();
mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this, mDrawableManager);
mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatCheckedTextView.java b/v7/appcompat/src/android/support/v7/widget/AppCompatCheckedTextView.java
index 29c877c..58098c08 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatCheckedTextView.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatCheckedTextView.java
@@ -45,7 +45,7 @@
}
public AppCompatCheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mTextHelper = AppCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 422ec1f..322bf81 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -16,8 +16,12 @@
package android.support.v7.widget;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -31,15 +35,20 @@
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.graphics.drawable.VectorDrawableCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.ColorUtils;
import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.util.ArrayMap;
import android.support.v4.util.LruCache;
import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
-import java.util.ArrayList;
+import java.lang.reflect.Type;
import java.util.WeakHashMap;
import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
@@ -52,27 +61,34 @@
public final class AppCompatDrawableManager {
public interface InflateDelegate {
- /**
- * Allows custom inflation of a drawable resource.
- *
- * @param context Context to inflate/create with
- * @param resId Resource ID of the drawable
- * @return the created drawable, or {@code null} to leave inflation to
- * AppCompatDrawableManager.
- */
- @Nullable
- Drawable onInflateDrawable(@NonNull Context context, @DrawableRes int resId);
+ Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme);
}
+ private static final InflateDelegate VDC_DELEGATE = new InflateDelegate() {
+ @Override
+ public Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
+ try {
+ return VectorDrawableCompat.createFromXmlInner(r, parser, attrs, theme);
+ } catch (Exception e) {
+ Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
+ return null;
+ }
+ }
+ };
+
private static final String TAG = "TintManager";
private static final boolean DEBUG = false;
private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
+ private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";
private static AppCompatDrawableManager INSTANCE;
public static AppCompatDrawableManager get() {
if (INSTANCE == null) {
INSTANCE = new AppCompatDrawableManager();
+ INSTANCE.addDelegate("vector", VDC_DELEGATE);
}
return INSTANCE;
}
@@ -94,18 +110,8 @@
* {@link DrawableCompat}'s tinting functionality.
*/
private static final int[] TINT_COLOR_CONTROL_NORMAL = {
- R.drawable.abc_ic_ab_back_mtrl_am_alpha,
- R.drawable.abc_ic_go_search_api_mtrl_alpha,
R.drawable.abc_ic_search_api_mtrl_alpha,
R.drawable.abc_ic_commit_search_api_mtrl_alpha,
- R.drawable.abc_ic_clear_mtrl_alpha,
- R.drawable.abc_ic_menu_share_mtrl_alpha,
- R.drawable.abc_ic_menu_copy_mtrl_am_alpha,
- R.drawable.abc_ic_menu_cut_mtrl_alpha,
- R.drawable.abc_ic_menu_selectall_mtrl_alpha,
- R.drawable.abc_ic_menu_paste_mtrl_am_alpha,
- R.drawable.abc_ic_menu_moreoverflow_mtrl_alpha,
- R.drawable.abc_ic_voice_search_api_mtrl_alpha
};
/**
@@ -134,16 +140,9 @@
* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}
*/
private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {
- R.drawable.abc_edit_text_material,
R.drawable.abc_tab_indicator_material,
R.drawable.abc_textfield_search_material,
- R.drawable.abc_spinner_mtrl_am_alpha,
- R.drawable.abc_spinner_textfield_background_material,
- R.drawable.abc_ratingbar_full_material,
- R.drawable.abc_switch_track_mtrl_alpha,
- R.drawable.abc_switch_thumb_material,
- R.drawable.abc_btn_default_mtrl_shape,
- R.drawable.abc_btn_borderless_material
+ R.drawable.abc_ratingbar_full_material
};
/**
@@ -157,7 +156,10 @@
};
private WeakHashMap<Context, SparseArray<ColorStateList>> mTintLists;
- private ArrayList<InflateDelegate> mDelegates;
+ private ArrayMap<String, InflateDelegate> mDelegates;
+ private SparseArray<String> mKnownDrawableIdTags;
+
+ private TypedValue mTypedValue;
public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
return getDrawable(context, resId, false);
@@ -165,71 +167,145 @@
public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,
boolean failIfNotKnown) {
- // Let the InflateDelegates have a go first
- if (mDelegates != null) {
- for (int i = 0, count = mDelegates.size(); i < count; i++) {
- final InflateDelegate delegate = mDelegates.get(i);
- final Drawable result = delegate.onInflateDrawable(context, resId);
- if (result != null) {
- return result;
- }
- }
+ Drawable drawable = loadDrawableFromDelegates(context, resId);
+ if (drawable == null) {
+ drawable = ContextCompat.getDrawable(context, resId);
}
-
- // The delegates failed so we'll carry on
- Drawable drawable = ContextCompat.getDrawable(context, resId);
-
if (drawable != null) {
- final ColorStateList tintList = getTintList(context, resId);
- if (tintList != null) {
- // First mutate the Drawable, then wrap it and set the tint list
- if (shouldMutateDrawable(drawable)) {
- drawable = drawable.mutate();
- }
- drawable = DrawableCompat.wrap(drawable);
- DrawableCompat.setTintList(drawable, tintList);
+ return tintDrawable(context, resId, failIfNotKnown, drawable);
+ }
+ return null;
+ }
- // If there is a blending mode specified for the drawable, use it
- final PorterDuff.Mode tintMode = getTintMode(resId);
- if (tintMode != null) {
- DrawableCompat.setTintMode(drawable, tintMode);
- }
- } else if (resId == R.drawable.abc_cab_background_top_material) {
- return new LayerDrawable(new Drawable[]{
- getDrawable(context, R.drawable.abc_cab_background_internal_bg),
- getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
- });
- } else if (resId == R.drawable.abc_seekbar_track_material) {
- LayerDrawable ld = (LayerDrawable) drawable;
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
- getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
- getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
- getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
- } else if (resId == R.drawable.abc_ratingbar_indicator_material
- || resId == R.drawable.abc_ratingbar_small_material) {
- LayerDrawable ld = (LayerDrawable) drawable;
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
- getDisabledThemeAttrColor(context, R.attr.colorControlNormal),
- DEFAULT_MODE);
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
- getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
- setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
- getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
- } else {
- final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);
- if (!tinted && failIfNotKnown) {
- // If we didn't tint using a ColorFilter, and we're set to fail if we don't
- // know the id, return null
- drawable = null;
- }
+ private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,
+ boolean failIfNotKnown, @NonNull Drawable drawable) {
+ final ColorStateList tintList = getTintList(context, resId);
+ if (tintList != null) {
+ // First mutate the Drawable, then wrap it and set the tint list
+ if (shouldMutateDrawable(drawable)) {
+ drawable = drawable.mutate();
+ }
+ drawable = DrawableCompat.wrap(drawable);
+ DrawableCompat.setTintList(drawable, tintList);
+
+ // If there is a blending mode specified for the drawable, use it
+ final PorterDuff.Mode tintMode = getTintMode(resId);
+ if (tintMode != null) {
+ DrawableCompat.setTintMode(drawable, tintMode);
+ }
+ } else if (resId == R.drawable.abc_cab_background_top_material) {
+ return new LayerDrawable(new Drawable[]{
+ getDrawable(context, R.drawable.abc_cab_background_internal_bg),
+ getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
+ });
+ } else if (resId == R.drawable.abc_seekbar_track_material) {
+ LayerDrawable ld = (LayerDrawable) drawable;
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
+ getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
+ getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
+ getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
+ } else if (resId == R.drawable.abc_ratingbar_indicator_material
+ || resId == R.drawable.abc_ratingbar_small_material) {
+ LayerDrawable ld = (LayerDrawable) drawable;
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),
+ getDisabledThemeAttrColor(context, R.attr.colorControlNormal),
+ DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),
+ getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
+ setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),
+ getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);
+ } else {
+ final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);
+ if (!tinted && failIfNotKnown) {
+ // If we didn't tint using a ColorFilter, and we're set to fail if we don't
+ // know the id, return null
+ drawable = null;
}
}
return drawable;
}
- public final boolean tintDrawableUsingColorFilter(@NonNull Context context,
+ private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {
+ if (mDelegates != null && !mDelegates.isEmpty()) {
+ String cachedTagName = null;
+
+ if (mKnownDrawableIdTags != null) {
+ cachedTagName = mKnownDrawableIdTags.get(resId);
+ if (SKIP_DRAWABLE_TAG.equals(cachedTagName)
+ || (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {
+ // If we don't have a delegate for the drawable tag, or we've been set to
+ // skip it, fail fast and return null
+ if (DEBUG) {
+ Log.d(TAG, "loadDrawableFromDelegates. Skipping drawable "
+ + context.getResources().getResourceName(resId));
+ }
+ return null;
+ }
+ } else {
+ // Create an id cache as we'll need one later
+ mKnownDrawableIdTags = new SparseArray<>();
+ }
+
+ if (mTypedValue == null) {
+ mTypedValue = new TypedValue();
+ }
+
+ final TypedValue tv = mTypedValue;
+ final Resources res = context.getResources();
+ res.getValue(resId, tv, true);
+
+ if (tv.string != null && tv.string.toString().endsWith(".xml")) {
+ // If the resource is an XML file, let's try and parse it
+ try {
+ final XmlPullParser parser = res.getXml(resId);
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ // Empty loop
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ final String tagName = parser.getName();
+ if (cachedTagName == null) {
+ // If we don't already have this cached, add it to the cache
+ mKnownDrawableIdTags.append(resId, tagName);
+ }
+
+ // Now try and find a delegate for the tag name and inflate if found
+ final InflateDelegate delegate = mDelegates.get(tagName);
+ if (delegate != null) {
+ return delegate.createFromXmlInner(res, parser, attrs, context.getTheme());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while inflating drawable", e);
+ }
+ }
+ }
+
+ // If we reach here then the delegate inflation of the resource failed. Mark it as
+ // bad so we skip the id next time
+ mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
+ return null;
+ }
+
+ public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
+ @NonNull TintResources resources, @DrawableRes final int resId) {
+ Drawable drawable = loadDrawableFromDelegates(context, resId);
+ if (drawable == null) {
+ drawable = resources.superGetDrawable(resId);
+ }
+ if (drawable != null) {
+ return tintDrawable(context, resId, false, drawable);
+ }
+ return null;
+ }
+
+ private static boolean tintDrawableUsingColorFilter(@NonNull Context context,
@DrawableRes final int resId, @NonNull Drawable drawable) {
PorterDuff.Mode tintMode = DEFAULT_MODE;
boolean colorAttrSet = false;
@@ -273,18 +349,16 @@
return false;
}
- public void addDelegate(@NonNull InflateDelegate delegate) {
+ public final void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
if (mDelegates == null) {
- mDelegates = new ArrayList<>();
+ mDelegates = new ArrayMap<>();
}
- if (!mDelegates.contains(delegate)) {
- mDelegates.add(delegate);
- }
+ mDelegates.put(tagName, delegate);
}
- public void removeDelegate(@NonNull InflateDelegate delegate) {
- if (mDelegates != null) {
- mDelegates.remove(delegate);
+ public final void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
+ if (mDelegates != null && mDelegates.get(tagName) == delegate) {
+ mDelegates.remove(tagName);
}
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java
index 18c07c9..379ae0c 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java
@@ -54,7 +54,7 @@
}
public AppCompatImageButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
index 89cc04d..6bfce53 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
@@ -18,13 +18,12 @@
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
+import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.widget.ImageView;
class AppCompatImageHelper {
- private static final int[] VIEW_ATTRS = {android.R.attr.src};
-
private final ImageView mView;
private final AppCompatDrawableManager mDrawableManager;
@@ -35,12 +34,20 @@
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
- VIEW_ATTRS, defStyleAttr, 0);
+ R.styleable.AppCompatImageView, defStyleAttr, 0);
try {
- final Drawable d = a.getDrawableIfKnown(0);
+ Drawable d = a.getDrawableIfKnown(R.styleable.AppCompatImageView_android_src);
if (d != null) {
mView.setImageDrawable(d);
}
+
+ final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1);
+ if (id != -1) {
+ d = mDrawableManager.getDrawable(mView.getContext(), id);
+ if (d != null) {
+ mView.setImageDrawable(d);
+ }
+ }
} finally {
a.recycle();
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java
index 0d85121..90bc173 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java
@@ -54,7 +54,7 @@
}
public AppCompatImageView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatRadioButton.java b/v7/appcompat/src/android/support/v7/widget/AppCompatRadioButton.java
index 3814a7e..cc1adc6 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatRadioButton.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatRadioButton.java
@@ -55,7 +55,7 @@
}
public AppCompatRadioButton(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mDrawableManager = AppCompatDrawableManager.get();
mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this, mDrawableManager);
mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextView.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextView.java
index b21f6bc..fdb4960 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextView.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextView.java
@@ -57,7 +57,7 @@
}
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
+ super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mDrawableManager = AppCompatDrawableManager.get();
mBackgroundTintHelper = new AppCompatBackgroundHelper(this, mDrawableManager);
diff --git a/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java b/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
deleted file mode 100644
index 828a792..0000000
--- a/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2015 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 org.xmlpull.v1.XmlPullParserException;
-
-import android.content.res.AssetFileDescriptor;
-import android.content.res.ColorStateList;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Movie;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.TypedValue;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * This extends Resources but delegates the calls to another Resources object. This enables
- * any customization done by some subclass of Resources to be also picked up.
- */
-class ResourcesWrapper extends Resources {
-
- private final Resources mResources;
-
- public ResourcesWrapper(Resources resources) {
- super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
- mResources = resources;
- }
-
- @Override
- public CharSequence getText(int id) throws NotFoundException {
- return mResources.getText(id);
- }
-
- @Override
- public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
- return mResources.getQuantityText(id, quantity);
- }
-
- @Override
- public String getString(int id) throws NotFoundException {
- return mResources.getString(id);
- }
-
- @Override
- public String getString(int id, Object... formatArgs) throws NotFoundException {
- return mResources.getString(id, formatArgs);
- }
-
- @Override
- public String getQuantityString(int id, int quantity, Object... formatArgs)
- throws NotFoundException {
- return mResources.getQuantityString(id, quantity, formatArgs);
- }
-
- @Override
- public String getQuantityString(int id, int quantity) throws NotFoundException {
- return mResources.getQuantityString(id, quantity);
- }
-
- @Override
- public CharSequence getText(int id, CharSequence def) {
- return mResources.getText(id, def);
- }
-
- @Override
- public CharSequence[] getTextArray(int id) throws NotFoundException {
- return mResources.getTextArray(id);
- }
-
- @Override
- public String[] getStringArray(int id) throws NotFoundException {
- return mResources.getStringArray(id);
- }
-
- @Override
- public int[] getIntArray(int id) throws NotFoundException {
- return mResources.getIntArray(id);
- }
-
- @Override
- public TypedArray obtainTypedArray(int id) throws NotFoundException {
- return mResources.obtainTypedArray(id);
- }
-
- @Override
- public float getDimension(int id) throws NotFoundException {
- return mResources.getDimension(id);
- }
-
- @Override
- public int getDimensionPixelOffset(int id) throws NotFoundException {
- return mResources.getDimensionPixelOffset(id);
- }
-
- @Override
- public int getDimensionPixelSize(int id) throws NotFoundException {
- return mResources.getDimensionPixelSize(id);
- }
-
- @Override
- public float getFraction(int id, int base, int pbase) {
- return mResources.getFraction(id, base, pbase);
- }
-
- @Override
- public Drawable getDrawable(int id) throws NotFoundException {
- return mResources.getDrawable(id);
- }
-
- @Override
- public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
- return mResources.getDrawable(id, theme);
- }
-
- @Override
- public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
- return mResources.getDrawableForDensity(id, density);
- }
-
- @Override
- public Drawable getDrawableForDensity(int id, int density, Theme theme) {
- return mResources.getDrawableForDensity(id, density, theme);
- }
-
- @Override
- public Movie getMovie(int id) throws NotFoundException {
- return mResources.getMovie(id);
- }
-
- @Override
- public int getColor(int id) throws NotFoundException {
- return mResources.getColor(id);
- }
-
- @Override
- public ColorStateList getColorStateList(int id) throws NotFoundException {
- return mResources.getColorStateList(id);
- }
-
- @Override
- public boolean getBoolean(int id) throws NotFoundException {
- return mResources.getBoolean(id);
- }
-
- @Override
- public int getInteger(int id) throws NotFoundException {
- return mResources.getInteger(id);
- }
-
- @Override
- public XmlResourceParser getLayout(int id) throws NotFoundException {
- return mResources.getLayout(id);
- }
-
- @Override
- public XmlResourceParser getAnimation(int id) throws NotFoundException {
- return mResources.getAnimation(id);
- }
-
- @Override
- public XmlResourceParser getXml(int id) throws NotFoundException {
- return mResources.getXml(id);
- }
-
- @Override
- public InputStream openRawResource(int id) throws NotFoundException {
- return mResources.openRawResource(id);
- }
-
- @Override
- public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
- return mResources.openRawResource(id, value);
- }
-
- @Override
- public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
- return mResources.openRawResourceFd(id);
- }
-
- @Override
- public void getValue(int id, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
- mResources.getValue(id, outValue, resolveRefs);
- }
-
- @Override
- public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
- mResources.getValueForDensity(id, density, outValue, resolveRefs);
- }
-
- @Override
- public void getValue(String name, TypedValue outValue, boolean resolveRefs)
- throws NotFoundException {
- mResources.getValue(name, outValue, resolveRefs);
- }
-
- @Override
- public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
- return mResources.obtainAttributes(set, attrs);
- }
-
- @Override
- public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
- super.updateConfiguration(config, metrics);
- if (mResources != null) { // called from super's constructor. So, need to check.
- mResources.updateConfiguration(config, metrics);
- }
- }
-
- @Override
- public DisplayMetrics getDisplayMetrics() {
- return mResources.getDisplayMetrics();
- }
-
- @Override
- public Configuration getConfiguration() {
- return mResources.getConfiguration();
- }
-
- @Override
- public int getIdentifier(String name, String defType, String defPackage) {
- return mResources.getIdentifier(name, defType, defPackage);
- }
-
- @Override
- public String getResourceName(int resid) throws NotFoundException {
- return mResources.getResourceName(resid);
- }
-
- @Override
- public String getResourcePackageName(int resid) throws NotFoundException {
- return mResources.getResourcePackageName(resid);
- }
-
- @Override
- public String getResourceTypeName(int resid) throws NotFoundException {
- return mResources.getResourceTypeName(resid);
- }
-
- @Override
- public String getResourceEntryName(int resid) throws NotFoundException {
- return mResources.getResourceEntryName(resid);
- }
-
- @Override
- public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
- throws XmlPullParserException, IOException {
- mResources.parseBundleExtras(parser, outBundle);
- }
-
- @Override
- public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
- throws XmlPullParserException {
- mResources.parseBundleExtra(tagName, attrs, outBundle);
- }
-}
-
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index ef12e06..2503fe4 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -20,6 +20,7 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
/**
* A {@link android.content.ContextWrapper} which returns a tint-aware
@@ -35,40 +36,32 @@
}
private Resources mResources;
+ private final Resources.Theme mTheme;
- private TintContextWrapper(Context base) {
+ private TintContextWrapper(@NonNull final Context base) {
super(base);
+
+ // We need to create a copy of the Theme so that the Theme references our Resources
+ // instance
+ mTheme = getResources().newTheme();
+ mTheme.setTo(base.getTheme());
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return mTheme;
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ mTheme.applyStyle(resid, true);
}
@Override
public Resources getResources() {
if (mResources == null) {
- mResources = new TintResources(super.getResources());
+ mResources = new TintResources(this, super.getResources());
}
return mResources;
}
-
- /**
- * This class allows us to intercept calls so that we can tint resources (if applicable).
- */
- class TintResources extends ResourcesWrapper {
- public TintResources(Resources resources) {
- super(resources);
- }
-
- /**
- * We intercept this call so that we tint the result (if applicable). This is needed for
- * things like {@link android.graphics.drawable.DrawableContainer}s which can retrieve
- * their children via this method.
- */
- @Override
- public Drawable getDrawable(int id) throws NotFoundException {
- Drawable d = super.getDrawable(id);
- if (d != null) {
- AppCompatDrawableManager.get().tintDrawableUsingColorFilter(
- TintContextWrapper.this, id, d);
- }
- return d;
- }
- }
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/TintResources.java b/v7/appcompat/src/android/support/v7/widget/TintResources.java
new file mode 100644
index 0000000..bd63634
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/TintResources.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+
+/**
+ * This class allows us to intercept calls so that we can tint resources (if applicable).
+ */
+class TintResources extends Resources {
+ private final Context mContext;
+
+ TintResources(@NonNull final Context context, @NonNull final Resources res) {
+ super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+ mContext = context;
+ }
+
+ /**
+ * We intercept this call so that we tint the result (if applicable). This is needed for
+ * things like {@link android.graphics.drawable.DrawableContainer}s which can retrieve
+ * their children via this method.
+ */
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ return AppCompatDrawableManager.get().onDrawableLoadedFromResources(mContext, this, id);
+ }
+
+ final Drawable superGetDrawable(int id) {
+ return super.getDrawable(id);
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
index 0576c15..cb88cfd 100644
--- a/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
@@ -89,7 +89,7 @@
public ToolbarWidgetWrapper(Toolbar toolbar, boolean style) {
this(toolbar, style, R.string.abc_action_bar_up_description,
- R.drawable.abc_ic_ab_back_mtrl_am_alpha);
+ R.drawable.abc_ic_ab_back_material);
}
public ToolbarWidgetWrapper(Toolbar toolbar, boolean style,
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
new file mode 100644
index 0000000..d91ce9f
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteCursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.support.test.espresso.DataInteraction;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtilsMatchers;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.widget.Button;
+import android.widget.CheckedTextView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import org.hamcrest.Matcher;
+
+import java.io.File;
+
+import static android.support.test.espresso.Espresso.onData;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.isDialog;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.hamcrest.core.Is.is;
+
+public class AlertDialogCursorTest
+ extends ActivityInstrumentationTestCase2<AlertDialogTestActivity> {
+
+ private Button mButton;
+
+ private int mClickedItemIndex = -1;
+
+ private static final String TEXT_COLUMN_NAME = "text";
+ private static final String CHECKED_COLUMN_NAME = "checked";
+
+ private String[] mTextContent;
+ private boolean[] mCheckedContent;
+
+ private String[] mProjectionWithChecked;
+ private String[] mProjectionWithoutChecked;
+
+ private SQLiteDatabase mDatabase;
+ private File mDatabaseFile;
+ private Cursor mCursor;
+
+ private AlertDialog mAlertDialog;
+
+ public AlertDialogCursorTest() {
+ super(AlertDialogTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ // Ideally these constant arrays would be defined as final static fields on the
+ // class level, but for some reason those get reset to null on v9- devices after
+ // the first test method has been executed.
+ mTextContent = new String[] { "Adele", "Beyonce", "Ciara", "Dido" };
+ mCheckedContent = new boolean[] { false, false, true, false };
+
+ mProjectionWithChecked = new String[] {
+ "_id", // 0
+ TEXT_COLUMN_NAME, // 1
+ CHECKED_COLUMN_NAME // 2
+ };
+ mProjectionWithoutChecked = new String[] {
+ "_id", // 0
+ TEXT_COLUMN_NAME // 1
+ };
+
+ final AlertDialogTestActivity activity = getActivity();
+ mButton = (Button) activity.findViewById(R.id.test_button);
+
+ Context context = getInstrumentation().getTargetContext();
+ File dbDir = context.getDir("tests", Context.MODE_PRIVATE);
+ mDatabaseFile = new File(dbDir, "database_alert_dialog_test.db");
+ if (mDatabaseFile.exists()) {
+ mDatabaseFile.delete();
+ }
+ mDatabase = SQLiteDatabase.openOrCreateDatabase(mDatabaseFile.getPath(), null);
+ assertNotNull(mDatabase);
+ // Create and populate a test table
+ mDatabase.execSQL(
+ "CREATE TABLE test (_id INTEGER PRIMARY KEY, " + TEXT_COLUMN_NAME +
+ " TEXT, " + CHECKED_COLUMN_NAME + " INTEGER);");
+ for (int i = 0; i < mTextContent.length; i++) {
+ mDatabase.execSQL("INSERT INTO test (" + TEXT_COLUMN_NAME + ", " +
+ CHECKED_COLUMN_NAME + ") VALUES ('" + mTextContent[i] + "', " +
+ (mCheckedContent[i] ? "1" : "0") + ");");
+ }
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ if (mCursor != null) {
+ try {
+ // Close the cursor on the UI thread as the list view in the alert dialog
+ // will get notified of any change to the underlying cursor.
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mCursor.close();
+ mCursor = null;
+ }
+ });
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ if (mDatabase != null) {
+ mDatabase.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
+ super.tearDown();
+ }
+
+ private void wireBuilder(final AlertDialog.Builder builder) {
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.show();
+ }
+ });
+ }
+
+ private void verifySimpleItemsContent(String[] expectedContent) {
+ final int expectedCount = expectedContent.length;
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ final ListView listView = mAlertDialog.getListView();
+ assertNotNull("List view is shown", listView);
+
+ final ListAdapter listAdapter = listView.getAdapter();
+ assertEquals("List has " + expectedCount + " entries",
+ expectedCount, listAdapter.getCount());
+
+ // Test that all items are showing
+ onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+ for (int i = 0; i < expectedCount; i++) {
+ DataInteraction rowInteraction = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME, expectedContent[i])));
+ rowInteraction.inRoot(isDialog()).check(matches(isDisplayed()));
+ }
+
+ // Test that a click on an item invokes the registered listener
+ assertEquals("Before list item click", -1, mClickedItemIndex);
+ int indexToClick = expectedCount - 2;
+ DataInteraction interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(
+ TEXT_COLUMN_NAME, expectedContent[indexToClick])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ assertEquals("List item clicked", indexToClick, mClickedItemIndex);
+ }
+
+ @SmallTest
+ public void testSimpleItemsFromCursor() {
+ mCursor = mDatabase.query("test", mProjectionWithoutChecked,
+ null, null, null, null, null);
+ assertNotNull(mCursor);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setCursor(mCursor,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mClickedItemIndex = which;
+ }
+ }, "text");
+ wireBuilder(builder);
+
+ verifySimpleItemsContent(mTextContent);
+ }
+
+ /**
+ * Helper method to verify the state of the multi-choice items list. It gets the String
+ * array of content and verifies that:
+ *
+ * 1. The items in the array are rendered as CheckedTextViews inside a ListView
+ * 2. Each item in the array is displayed
+ * 3. Checked state of each row in the ListView corresponds to the matching entry in the
+ * passed boolean array
+ */
+ private void verifyMultiChoiceItemsState(String[] expectedContent,
+ boolean[] checkedTracker) {
+ final int expectedCount = expectedContent.length;
+
+ final ListView listView = mAlertDialog.getListView();
+ assertNotNull("List view is shown", listView);
+
+ final ListAdapter listAdapter = listView.getAdapter();
+ assertEquals("List has " + expectedCount + " entries",
+ expectedCount, listAdapter.getCount());
+
+ for (int i = 0; i < expectedCount; i++) {
+ Matcher checkedStateMatcher = checkedTracker[i] ? TestUtilsMatchers.isCheckedTextView() :
+ TestUtilsMatchers.isNonCheckedTextView();
+ // Check that the corresponding row is rendered as CheckedTextView with expected
+ // checked state.
+ DataInteraction rowInteraction = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME, expectedContent[i])));
+ rowInteraction.inRoot(isDialog()).
+ check(matches(allOf(
+ isDisplayed(),
+ isAssignableFrom(CheckedTextView.class),
+ isDescendantOfA(isAssignableFrom(ListView.class)),
+ checkedStateMatcher)));
+ }
+ }
+
+ private void verifyMultiChoiceItemsContent(String[] expectedContent,
+ final boolean[] checkedTracker) {
+ final int expectedCount = expectedContent.length;
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ final ListView listView = mAlertDialog.getListView();
+ assertNotNull("List view is shown", listView);
+
+ final ListAdapter listAdapter = listView.getAdapter();
+ assertEquals("List has " + expectedCount + " entries",
+ expectedCount, listAdapter.getCount());
+
+ // Test that all items are showing
+ onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+ verifyMultiChoiceItemsState(expectedContent, checkedTracker);
+
+ // We're going to click item #1 and test that the click listener has been invoked to
+ // update the original state array
+ boolean[] expectedAfterClick1 = checkedTracker.clone();
+ expectedAfterClick1[1] = !expectedAfterClick1[1];
+ DataInteraction interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME, expectedContent[1])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1);
+
+ // Now click item #1 again and test that the click listener has been invoked to update the
+ // original state array again
+ expectedAfterClick1[1] = !expectedAfterClick1[1];
+ interactionForClick.inRoot(isDialog()).perform(click());
+ verifyMultiChoiceItemsState(expectedContent, expectedAfterClick1);
+
+ // Now we're going to click the last item and test that the click listener has been invoked
+ // to update the original state array
+ boolean[] expectedAfterClickLast = checkedTracker.clone();
+ expectedAfterClickLast[expectedCount - 1] = !expectedAfterClickLast[expectedCount - 1];
+ interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME,
+ expectedContent[expectedCount - 1])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ verifyMultiChoiceItemsState(expectedContent, expectedAfterClickLast);
+ }
+
+ @SmallTest
+ public void testMultiChoiceItemsFromCursor() {
+ mCursor = mDatabase.query("test", mProjectionWithChecked,
+ null, null, null, null, null);
+ assertNotNull(mCursor);
+
+ final boolean[] checkedTracker = mCheckedContent.clone();
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMultiChoiceItems(mCursor, CHECKED_COLUMN_NAME, TEXT_COLUMN_NAME,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which,
+ boolean isChecked) {
+ // Update the underlying database with the new checked
+ // state for the specific row
+ mCursor.moveToPosition(which);
+ ContentValues valuesToUpdate = new ContentValues();
+ valuesToUpdate.put(CHECKED_COLUMN_NAME, isChecked ? 1 : 0);
+ mDatabase.update("test", valuesToUpdate,
+ TEXT_COLUMN_NAME + " = ?",
+ new String[] { mCursor.getString(1) } );
+ mCursor.requery();
+ checkedTracker[which] = isChecked;
+ }
+ });
+ wireBuilder(builder);
+
+ // Pass the same boolean[] array as used for initialization since our click listener
+ // will be updating its content.
+ verifyMultiChoiceItemsContent(mTextContent, checkedTracker);
+ }
+
+ /**
+ * Helper method to verify the state of the single-choice items list. It gets the String
+ * array of content and verifies that:
+ *
+ * 1. The items in the array are rendered as CheckedTextViews inside a ListView
+ * 2. Each item in the array is displayed
+ * 3. Only one row in the ListView is checked, and that corresponds to the passed
+ * integer index.
+ */
+ private void verifySingleChoiceItemsState(String[] expectedContent,
+ int currentlyExpectedSelectionIndex) {
+ final int expectedCount = expectedContent.length;
+
+ final ListView listView = mAlertDialog.getListView();
+ assertNotNull("List view is shown", listView);
+
+ final ListAdapter listAdapter = listView.getAdapter();
+ assertEquals("List has " + expectedCount + " entries",
+ expectedCount, listAdapter.getCount());
+
+ for (int i = 0; i < expectedCount; i++) {
+ Matcher checkedStateMatcher = (i == currentlyExpectedSelectionIndex) ?
+ TestUtilsMatchers.isCheckedTextView() :
+ TestUtilsMatchers.isNonCheckedTextView();
+ // Check that the corresponding row is rendered as CheckedTextView with expected
+ // checked state.
+ DataInteraction rowInteraction = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME, expectedContent[i])));
+ rowInteraction.inRoot(isDialog()).
+ check(matches(allOf(
+ isDisplayed(),
+ isAssignableFrom(CheckedTextView.class),
+ isDescendantOfA(isAssignableFrom(ListView.class)),
+ checkedStateMatcher)));
+ }
+ }
+
+ private void verifySingleChoiceItemsContent(String[] expectedContent,
+ int initialSelectionIndex) {
+ final int expectedCount = expectedContent.length;
+ int currentlyExpectedSelectionIndex = initialSelectionIndex;
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Test that all items are showing
+ onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+ // We're going to click the first unselected item and test that the click listener has
+ // been invoked.
+ currentlyExpectedSelectionIndex = (currentlyExpectedSelectionIndex == 0) ? 1 : 0;
+ DataInteraction interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME,
+ expectedContent[currentlyExpectedSelectionIndex])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ assertEquals("Selected first single-choice item",
+ currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+ // Now click the same item again and test that the selection has not changed
+ interactionForClick.inRoot(isDialog()).perform(click());
+ assertEquals("Selected first single-choice item again",
+ currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+ // Now we're going to click the last item and test that the click listener has been invoked
+ // to update the original state array
+ currentlyExpectedSelectionIndex = expectedCount - 1;
+ interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME,
+ expectedContent[currentlyExpectedSelectionIndex])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ assertEquals("Selected last single-choice item",
+ currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+ }
+
+ @SmallTest
+ public void testSingleChoiceItemsFromCursor() {
+ mCursor = mDatabase.query("test", mProjectionWithoutChecked,
+ null, null, null, null, null);
+ assertNotNull(mCursor);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setSingleChoiceItems(mCursor, 2, TEXT_COLUMN_NAME,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mClickedItemIndex = which;
+ }
+ });
+ wireBuilder(builder);
+
+ verifySingleChoiceItemsContent(mTextContent, 2);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
index 28ed880..854adb5 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
@@ -17,6 +17,8 @@
package android.support.v7.app;
import android.support.v7.testutils.BaseTestActivity;
+import android.test.suitebuilder.annotation.SmallTest;
+
import org.junit.Test;
public abstract class BaseBasicsTestCase<A extends BaseTestActivity>
@@ -27,16 +29,19 @@
}
@Test
+ @SmallTest
public void testActionBarExists() {
assertNotNull("ActionBar is not null", getActivity().getSupportActionBar());
}
@Test
+ @SmallTest
public void testDefaultActionBarTitle() {
assertEquals(getActivity().getTitle(), getActivity().getSupportActionBar().getTitle());
}
@Test
+ @SmallTest
public void testSetActionBarTitle() throws Throwable {
final String newTitle = "hello";
runTestOnUiThread(new Runnable() {
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
index 91b791f..d0beb59 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
@@ -21,6 +21,8 @@
import android.support.v7.appcompat.test.R;
import android.support.v7.view.ActionMode;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
@@ -35,6 +37,7 @@
}
@Test
+ @SmallTest
public void testBackDismissesActionMode() {
final AtomicBoolean destroyed = new AtomicBoolean();
@@ -75,6 +78,7 @@
}
@Test
+ @SmallTest
public void testBackCollapsesSearchView() throws InterruptedException {
// First expand the SearchView
getActivity().runOnUiThread(new Runnable() {
@@ -102,6 +106,7 @@
}
@Test
+ @SmallTest
public void testMenuPressInvokesPanelCallbacks() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
@@ -113,6 +118,7 @@
}
@Test
+ @SmallTest
public void testBackPressWithMenuInvokesOnPanelClosed() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
@@ -123,6 +129,7 @@
}
@Test
+ @MediumTest
public void testBackPressWithEmptyMenuDestroysActivity() throws InterruptedException {
repopulateWithEmptyMenu();
@@ -134,6 +141,7 @@
}
@Test
+ @SmallTest
public void testDelKeyEventReachesActivity() {
// First send the event
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
@@ -149,6 +157,7 @@
}
@Test
+ @SmallTest
public void testMenuKeyEventReachesActivity() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
index 73d1e61..b118eec 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
@@ -21,6 +21,7 @@
import android.os.SystemClock;
import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MenuItem;
@@ -33,6 +34,7 @@
}
@Test
+ @SmallTest
public void testAlphabeticCtrlShortcut() {
testKeyboardShortcut(KeyEvent.KEYCODE_A,
KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON,
diff --git a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
index e1f9aa2..785dcc1 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
@@ -28,6 +28,7 @@
import android.support.v7.widget.AppCompatRadioButton;
import android.support.v7.widget.AppCompatRatingBar;
import android.support.v7.widget.AppCompatSpinner;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.LayoutInflater;
import android.view.View;
@@ -38,6 +39,7 @@
}
@Test
+ @SmallTest
public void testAndroidThemeInflation() throws Throwable {
if (Build.VERSION.SDK_INT < 10) {
// Ignore this test if running on Gingerbread or below
@@ -54,6 +56,7 @@
}
@Test
+ @SmallTest
public void testAppThemeInflation() throws Throwable {
if (Build.VERSION.SDK_INT < 10) {
// Ignore this test if running on Gingerbread or below
@@ -70,42 +73,50 @@
}
@Test
+ @SmallTest
public void testSpinnerInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_spinner, AppCompatSpinner.class);
}
@Test
+ @SmallTest
public void testEditTextInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_edittext, AppCompatEditText.class);
}
@Test
+ @SmallTest
public void testButtonInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_button, AppCompatButton.class);
}
@Test
+ @SmallTest
public void testRadioButtonInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_radiobutton, AppCompatRadioButton.class);
}
@Test
+ @SmallTest
public void testCheckBoxInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_checkbox, AppCompatCheckBox.class);
}
@Test
+ @SmallTest
public void testActvInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_actv, AppCompatAutoCompleteTextView.class);
}
@Test
+ @SmallTest
public void testMactvInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_mactv,
AppCompatMultiAutoCompleteTextView.class);
}
@Test
+ @SmallTest
public void testRatingBarInflation() throws Throwable {
testAppCompatWidgetInflation(R.layout.layout_ratingbar, AppCompatRatingBar.class);
}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
index 319583e..2b75600 100644
--- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
@@ -16,9 +16,11 @@
package android.support.v7.testutils;
+import android.database.sqlite.SQLiteCursor;
import android.graphics.drawable.Drawable;
import android.support.annotation.ColorInt;
import android.support.test.espresso.matcher.BoundedMatcher;
+import android.text.TextUtils;
import android.view.View;
import android.widget.CheckedTextView;
import android.widget.ImageView;
@@ -117,4 +119,24 @@
}
};
}
+
+ /**
+ * Returns a matcher that matches data entry in <code>SQLiteCursor</code> that has
+ * the specified text in the specified column.
+ */
+ public static Matcher<Object> withCursorItemContent(final String columnName,
+ final String expectedText) {
+ return new BoundedMatcher<Object, SQLiteCursor>(SQLiteCursor.class) {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("doesn't match " + expectedText);
+ }
+
+ @Override
+ protected boolean matchesSafely(SQLiteCursor cursor) {
+ return TextUtils.equals(expectedText,
+ cursor.getString(cursor.getColumnIndex(columnName)));
+ }
+ };
+ }
}
diff --git a/v7/gridlayout/tests/AndroidManifest.xml b/v7/gridlayout/tests/AndroidManifest.xml
index c55a49a..bc50c89 100644
--- a/v7/gridlayout/tests/AndroidManifest.xml
+++ b/v7/gridlayout/tests/AndroidManifest.xml
@@ -24,7 +24,5 @@
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.support.v7.gridlayout.test"
- android:label="GridLayout Tests" />
-
+ android:targetPackage="android.support.v7.gridlayout.test"/>
</manifest>
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
index 1a88be5..aa81870 100644
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
@@ -20,7 +20,8 @@
import android.os.Debug;
import android.support.v7.widget.GridLayout;
import android.test.ActivityInstrumentationTestCase2;
-import android.support.v7.gridlayout.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.support.v7.gridlayout.test.R;
import android.test.UiThreadTest;
import android.view.Gravity;
import android.view.View;
@@ -30,6 +31,7 @@
/**
* @hide
*/
+@SmallTest
public class GridLayoutTest extends ActivityInstrumentationTestCase2 {
public GridLayoutTest() {
diff --git a/v7/palette/src/androidTest/NO_DOCS b/v7/palette/src/androidTest/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v7/palette/src/androidTest/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2015 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.
+
+Having this file, named NO_DOCS, in a directory will prevent
+Android javadocs from being generated for java files under
+the directory. This is especially useful for test projects.
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index 6bc9576..d74c0e5 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -265,6 +265,8 @@
method public int computeVerticalScrollRange();
method public boolean drawChild(android.graphics.Canvas, android.view.View, long);
method public android.view.View findChildViewUnder(float, float);
+ method public android.view.View findContainingItemView(android.view.View);
+ method public android.support.v7.widget.RecyclerView.ViewHolder findContainingViewHolder(android.view.View);
method public android.support.v7.widget.RecyclerView.ViewHolder findViewHolderForAdapterPosition(int);
method public android.support.v7.widget.RecyclerView.ViewHolder findViewHolderForItemId(long);
method public android.support.v7.widget.RecyclerView.ViewHolder findViewHolderForLayoutPosition(int);
@@ -469,6 +471,7 @@
method public void detachView(android.view.View);
method public void detachViewAt(int);
method public void endAnimation(android.view.View);
+ method public android.view.View findContainingItemView(android.view.View);
method public android.view.View findViewByPosition(int);
method public abstract android.support.v7.widget.RecyclerView.LayoutParams generateDefaultLayoutParams();
method public android.support.v7.widget.RecyclerView.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams);
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index c9f1a22..c556fc5 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -5,9 +5,16 @@
dependencies {
compile project(':support-v4')
compile project(':support-annotations')
- androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
- androidTestCompile 'com.android.support.test.espresso:espresso-core:2.0'
+ androidTestCompile ('com.android.support.test:runner:0.4.1') {
+ exclude module: 'support-annotations'
+ }
+ androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
+ exclude module: 'support-annotations'
+ }
testCompile 'junit:junit:4.12'
+ androidTestCompile "org.mockito:mockito-core:1.9.5"
+ androidTestCompile "com.google.dexmaker:dexmaker:1.2"
+ androidTestCompile "com.google.dexmaker:dexmaker-mockito:1.2"
}
android {
@@ -43,6 +50,10 @@
testOptions {
unitTests.returnDefaultValues = true
}
+
+ buildTypes.all {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
}
android.libraryVariants.all { variant ->
@@ -78,17 +89,6 @@
artifacts.add('archives', sourcesJarTask);
}
-// TODO make this generic for all projects
-afterEvaluate {
- def originalTask = tasks['packageDebugAndroidTest']
- tasks['assembleDebugAndroidTest'].doLast {
- copy {
- from(originalTask.outputFile)
- into(rootProject.ext.testApkDistOut)
- }
- }
-}
-
uploadArchives {
repositories {
mavenDeployer {
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java
index b3ab064..e803d2a 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListAdapterCallbackWrapperTest.java
@@ -22,6 +22,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
import static android.support.v7.util.SortedList.BatchedCallback.TYPE_NONE;
import static android.support.v7.util.SortedList.BatchedCallback.TYPE_ADD;
import static android.support.v7.util.SortedList.BatchedCallback.TYPE_REMOVE;
@@ -29,6 +31,7 @@
import static android.support.v7.util.SortedList.BatchedCallback.TYPE_MOVE;
@RunWith(JUnit4.class)
+@SmallTest
public class SortedListAdapterCallbackWrapperTest extends TestCase {
private int lastReceivedType = TYPE_NONE;
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListTest.java
index 03c1151..9bcae5c 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/util/SortedListTest.java
@@ -23,6 +23,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -31,6 +33,7 @@
import java.util.Random;
@RunWith(JUnit4.class)
+@SmallTest
public class SortedListTest extends TestCase {
SortedList<Item> mList;
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
index ba6ec71..09f7336 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/AdapterHelperTest.java
@@ -25,6 +25,7 @@
import org.junit.runners.JUnit4;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
@@ -39,6 +40,7 @@
import static android.support.v7.widget.RecyclerView.*;
@RunWith(JUnit4.class)
+@SmallTest
public class AdapterHelperTest extends AndroidTestCase {
private static final boolean DEBUG = false;
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/OpReorderTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/OpReorderTest.java
index 06bfce6..884cdd5 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/OpReorderTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/OpReorderTest.java
@@ -28,6 +28,7 @@
import java.util.Set;
import android.support.v7.widget.AdapterHelper.UpdateOp;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;
import static android.support.v7.widget.AdapterHelper.UpdateOp.ADD;
@@ -37,6 +38,7 @@
import static org.junit.Assert.*;
@RunWith(JUnit4.class)
+@SmallTest
public class OpReorderTest {
private static final String TAG = "OpReorderTest";
diff --git a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
index 559bc6b..f460f79 100644
--- a/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ b/v7/recyclerview/jvm-tests/src/android/support/v7/widget/ViewInfoStoreTest.java
@@ -26,6 +26,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.Pair;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import java.util.ArrayList;
@@ -42,6 +43,7 @@
@SuppressWarnings("ConstantConditions")
@RunWith(JUnit4.class)
+@SmallTest
public class ViewInfoStoreTest extends TestCase {
ViewInfoStore mStore;
LoggingProcessCallback mCallback;
diff --git a/v7/recyclerview/proguard-rules.pro b/v7/recyclerview/proguard-rules.pro
new file mode 100644
index 0000000..113d94d
--- /dev/null
+++ b/v7/recyclerview/proguard-rules.pro
@@ -0,0 +1,19 @@
+# Copyright (C) 2015 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.
+
+# When layoutManager xml attribute is used, RecyclerView inflates
+#LayoutManagers' constructors using reflection.
+-keep public class * extends android.support.v7.widget.RecyclerView$LayoutManager {
+ public <init>(...);
+}
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index 7dcaea0..e182c93 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -842,6 +842,78 @@
}
@Override
+ public View onFocusSearchFailed(View focused, int focusDirection,
+ RecyclerView.Recycler recycler, RecyclerView.State state) {
+ View prevFocusedChild = findContainingItemView(focused);
+ if (prevFocusedChild == null) {
+ return null;
+ }
+ LayoutParams lp = (LayoutParams) prevFocusedChild.getLayoutParams();
+ final int prevSpanStart = lp.mSpanIndex;
+ final int prevSpanEnd = lp.mSpanIndex + lp.mSpanSize;
+ View view = super.onFocusSearchFailed(focused, focusDirection, recycler, state);
+ if (view == null) {
+ return null;
+ }
+ // LinearLayoutManager finds the last child. What we want is the child which has the same
+ // spanIndex.
+ final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
+ final boolean ascend = (layoutDir == LayoutState.LAYOUT_END) != mShouldReverseLayout;
+ final int start, inc, limit;
+ if (ascend) {
+ start = getChildCount() - 1;
+ inc = -1;
+ limit = -1;
+ } else {
+ start = 0;
+ inc = 1;
+ limit = getChildCount();
+ }
+ final boolean preferLastSpan = mOrientation == VERTICAL && isLayoutRTL();
+ View weakCandidate = null; // somewhat matches but not strong
+ int weakCandidateSpanIndex = -1;
+ int weakCandidateOverlap = 0; // how many spans overlap
+
+ for (int i = start; i != limit; i += inc) {
+ View candidate = getChildAt(i);
+ if (candidate == prevFocusedChild) {
+ break;
+ }
+ if (!candidate.isFocusable()) {
+ continue;
+ }
+ final LayoutParams candidateLp = (LayoutParams) candidate.getLayoutParams();
+ final int candidateStart = candidateLp.mSpanIndex;
+ final int candidateEnd = candidateLp.mSpanIndex + candidateLp.mSpanSize;
+ if (candidateStart == prevSpanStart && candidateEnd == prevSpanEnd) {
+ return candidate; // perfect match
+ }
+ boolean assignAsWeek = false;
+ if (weakCandidate == null) {
+ assignAsWeek = true;
+ } else {
+ int maxStart = Math.max(candidateStart, prevSpanStart);
+ int minEnd = Math.min(candidateEnd, prevSpanEnd);
+ int overlap = minEnd - maxStart;
+ if (overlap > weakCandidateOverlap) {
+ assignAsWeek = true;
+ } else if (overlap == weakCandidateOverlap &&
+ preferLastSpan == (candidateStart > weakCandidateSpanIndex)) {
+ assignAsWeek = true;
+ }
+ }
+
+ if (assignAsWeek) {
+ weakCandidate = candidate;
+ weakCandidateSpanIndex = candidateLp.mSpanIndex;
+ weakCandidateOverlap = Math.min(candidateEnd, prevSpanEnd) -
+ Math.max(candidateStart, prevSpanStart);
+ }
+ }
+ return weakCandidate;
+ }
+
+ @Override
public boolean supportsPredictiveItemAnimations() {
return mPendingSavedState == null && !mPendingSpanCountChange;
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
index f58a4a7..2402313 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
@@ -70,6 +70,11 @@
int mEndLine = 0;
/**
+ * If true, layout should stop if a focusable view is added
+ */
+ boolean mStopInFocusable;
+
+ /**
* @return true if there are more items in the data adapter
*/
boolean hasMore(RecyclerView.State state) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index b896902..bef7d6a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -58,7 +58,7 @@
* than this factor times the total space of the list. If layout is vertical, total space is the
* height minus padding, if layout is horizontal, total space is the width minus padding.
*/
- private static final float MAX_SCROLL_FACTOR = 0.33f;
+ private static final float MAX_SCROLL_FACTOR = 1 / 3f;
/**
@@ -1449,7 +1449,7 @@
* @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
* is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
*/
- private int convertFocusDirectionToLayoutDirection(int focusDirection) {
+ int convertFocusDirectionToLayoutDirection(int focusDirection) {
switch (focusDirection) {
case View.FOCUS_BACKWARD:
return LayoutState.LAYOUT_START;
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index 5808604..edf9a45 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -3472,6 +3472,44 @@
return getChildViewHolderInt(child);
}
+ /**
+ * Traverses the ascendants of the given view and returns the item view that contains it and
+ * also a direct child of the RecyclerView. This returned view can be used to get the
+ * ViewHolder by calling {@link #getChildViewHolder(View)}.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The direct child of the RecyclerView which contains the given view or null if the
+ * provided view is not a descendant of this RecyclerView.
+ *
+ * @see #getChildViewHolder(View)
+ * @see #findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ ViewParent parent = view.getParent();
+ while (parent != null && parent != this && parent instanceof View) {
+ view = (View) parent;
+ parent = view.getParent();
+ }
+ return parent == this ? view : null;
+ }
+
+ /**
+ * Returns the ViewHolder that contains the given view.
+ *
+ * @param view The view that is a descendant of the RecyclerView.
+ *
+ * @return The ViewHolder that contains the given view or null if the provided view is not a
+ * descendant of this RecyclerView.
+ */
+ @Nullable
+ public ViewHolder findContainingViewHolder(View view) {
+ View itemView = findContainingItemView(view);
+ return itemView == null ? null : getChildViewHolder(itemView);
+ }
+
+
static ViewHolder getChildViewHolderInt(View child) {
if (child == null) {
return null;
@@ -6409,6 +6447,36 @@
}
/**
+ * Traverses the ascendants of the given view and returns the item view that contains it
+ * and also a direct child of the LayoutManager.
+ * <p>
+ * Note that this method may return null if the view is a child of the RecyclerView but
+ * not a child of the LayoutManager (e.g. running a disappear animation).
+ *
+ * @param view The view that is a descendant of the LayoutManager.
+ *
+ * @return The direct child of the LayoutManager which contains the given view or null if
+ * the provided view is not a descendant of this LayoutManager.
+ *
+ * @see RecyclerView#getChildViewHolder(View)
+ * @see RecyclerView#findContainingViewHolder(View)
+ */
+ @Nullable
+ public View findContainingItemView(View view) {
+ if (mRecyclerView == null) {
+ return null;
+ }
+ View found = mRecyclerView.findContainingItemView(view);
+ if (found == null) {
+ return null;
+ }
+ if (mChildHelper.isHidden(found)) {
+ return null;
+ }
+ return found;
+ }
+
+ /**
* Finds the view which represents the given adapter position.
* <p>
* This method traverses each child since it has no information about child order.
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 071bb4e..3147d24 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -21,6 +21,7 @@
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
@@ -29,6 +30,7 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import java.util.ArrayList;
@@ -65,6 +67,7 @@
*/
public static final int GAP_HANDLING_NONE = 0;
+ @SuppressWarnings("unused")
@Deprecated
public static final int GAP_HANDLING_LAZY = 1;
@@ -90,6 +93,12 @@
public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
private static final int INVALID_OFFSET = Integer.MIN_VALUE;
+ /**
+ * While trying to find next view to focus, LayoutManager will not try to scroll more
+ * than this factor times the total space of the list. If layout is vertical, total space is the
+ * height minus padding, if layout is horizontal, total space is the width minus padding.
+ */
+ private static final float MAX_SCROLL_FACTOR = 1 / 3f;
/**
* Number of spans
@@ -205,6 +214,7 @@
* Constructor used when layout manager is set in XML by RecyclerView attribute
* "layoutManager". Defaults to single column and vertical.
*/
+ @SuppressWarnings("unused")
public StaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
@@ -1298,6 +1308,7 @@
mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
mLayoutState.mStartLine = -startExtra;
}
+ mLayoutState.mStopInFocusable = false;
}
private void setLayoutStateDirection(int direction) {
@@ -1493,7 +1504,6 @@
mLaidOutInvalidFullSpan = true;
}
}
-
}
attachViewToSpans(view, lp, layoutState);
final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
@@ -1512,6 +1522,13 @@
updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
}
recycle(recycler, mLayoutState);
+ if (mLayoutState.mStopInFocusable && view.isFocusable()) {
+ if (lp.mFullSpan) {
+ mRemainingSpans.clear();
+ } else {
+ mRemainingSpans.set(currentSpan.mIndex, false);
+ }
+ }
added = true;
}
if (!added) {
@@ -1563,6 +1580,9 @@
}
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
+ if (layoutState.mStopInFocusable) {
+ return;
+ }
if (layoutState.mAvailable == 0) {
// easy, recycle line is still valid
if (layoutState.mLayoutDirection == LAYOUT_START) {
@@ -2014,6 +2034,104 @@
return mOrientation;
}
+ @Nullable
+ @Override
+ public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return null;
+ }
+
+ View directChild = findContainingItemView(focused);
+ if (directChild == null) {
+ return null;
+ }
+
+ ensureOrientationHelper();
+ resolveShouldLayoutReverse();
+ final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
+ if (layoutDir == LayoutState.INVALID_LAYOUT) {
+ return null;
+ }
+ LayoutParams prevFocusLayoutParams = (LayoutParams) focused.getLayoutParams();
+ boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan;
+ final Span prevFocusSpan = prevFocusLayoutParams.mSpan;
+ final int referenceChildPosition;
+ if (layoutDir == LAYOUT_END) { // layout towards end
+ referenceChildPosition = getLastChildPosition();
+ } else {
+ referenceChildPosition = getFirstChildPosition();
+ }
+ updateLayoutState(referenceChildPosition, state);
+ setLayoutStateDirection(layoutDir);
+
+ mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
+ mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace());
+ mLayoutState.mStopInFocusable = true;
+ fill(recycler, mLayoutState, state);
+ mLastLayoutFromEnd = mShouldReverseLayout;
+ if (!prevFocusFullSpan) {
+ View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir);
+ if (view != null && view != focused) {
+ return view;
+ }
+ }
+ // either could not find from the desired span or prev view is full span.
+ // traverse all spans
+ if (preferLastSpan(layoutDir)) {
+ for (int i = mSpanCount - 1; i >= 0; i --) {
+ View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
+ if (view != null && view != focused) {
+ return view;
+ }
+ }
+ } else {
+ for (int i = 0; i < mSpanCount; i ++) {
+ View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
+ if (view != null && view != focused) {
+ return view;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Converts a focusDirection to orientation.
+ *
+ * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
+ * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
+ * or 0 for not applicable
+ * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
+ * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
+ */
+ private int convertFocusDirectionToLayoutDirection(int focusDirection) {
+ switch (focusDirection) {
+ case View.FOCUS_BACKWARD:
+ return LayoutState.LAYOUT_START;
+ case View.FOCUS_FORWARD:
+ return LayoutState.LAYOUT_END;
+ case View.FOCUS_UP:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_DOWN:
+ return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_LEFT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
+ : LayoutState.INVALID_LAYOUT;
+ case View.FOCUS_RIGHT:
+ return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
+ : LayoutState.INVALID_LAYOUT;
+ default:
+ if (DEBUG) {
+ Log.d(TAG, "Unknown focus request:" + focusDirection);
+ }
+ return LayoutState.INVALID_LAYOUT;
+ }
+
+ }
/**
* LayoutParams used by StaggeredGridLayoutManager.
@@ -2094,7 +2212,7 @@
class Span {
static final int INVALID_LINE = Integer.MIN_VALUE;
- private ArrayList<View> mViews = new ArrayList<View>();
+ private ArrayList<View> mViews = new ArrayList<>();
int mCachedStart = INVALID_LINE;
int mCachedEnd = INVALID_LINE;
int mDeletedSize = 0;
@@ -2278,45 +2396,6 @@
}
}
- // normalized offset is how much this span can scroll
- int getNormalizedOffset(int dt, int targetStart, int targetEnd) {
- if (mViews.size() == 0) {
- return 0;
- }
- if (dt < 0) {
- final int endSpace = getEndLine() - targetEnd;
- if (endSpace <= 0) {
- return 0;
- }
- return -dt > endSpace ? -endSpace : dt;
- } else {
- final int startSpace = targetStart - getStartLine();
- if (startSpace <= 0) {
- return 0;
- }
- return startSpace < dt ? startSpace : dt;
- }
- }
-
- /**
- * Returns if there is no child between start-end lines
- *
- * @param start The start line
- * @param end The end line
- * @return true if a new child can be added between start and end
- */
- boolean isEmpty(int start, int end) {
- final int count = mViews.size();
- for (int i = 0; i < count; i++) {
- final View view = mViews.get(i);
- if (mPrimaryOrientation.getDecoratedStart(view) < end &&
- mPrimaryOrientation.getDecoratedEnd(view) > start) {
- return false;
- }
- }
- return true;
- }
-
public int findFirstVisibleItemPosition() {
return mReverseLayout
? findOneVisibleChild(mViews.size() - 1, -1, false)
@@ -2361,6 +2440,36 @@
}
return NO_POSITION;
}
+
+ /**
+ * Depending on the layout direction, returns the View that is after the given position.
+ */
+ public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) {
+ View candidate = null;
+ if (layoutDir == LAYOUT_START) {
+ final int limit = mViews.size();
+ for (int i = 0; i < limit; i++) {
+ final View view = mViews.get(i);
+ if (view.isFocusable() &&
+ (getPosition(view) > referenceChildPosition == mReverseLayout) ) {
+ candidate = view;
+ } else {
+ break;
+ }
+ }
+ } else {
+ for (int i = mViews.size() - 1; i >= 0; i--) {
+ final View view = mViews.get(i);
+ if (view.isFocusable() &&
+ (getPosition(view) > referenceChildPosition == !mReverseLayout)) {
+ candidate = view;
+ } else {
+ break;
+ }
+ }
+ }
+ return candidate;
+ }
}
/**
@@ -2537,7 +2646,7 @@
public void addFullSpanItem(FullSpanItem fullSpanItem) {
if (mFullSpanItems == null) {
- mFullSpanItems = new ArrayList<FullSpanItem>();
+ mFullSpanItems = new ArrayList<>();
}
final int size = mFullSpanItems.size();
for (int i = 0; i < size; i++) {
@@ -2629,10 +2738,6 @@
return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
}
- public void invalidateSpanGaps() {
- mGapPerSpan = null;
- }
-
@Override
public int describeContents() {
return 0;
@@ -2712,6 +2817,7 @@
mReverseLayout = in.readInt() == 1;
mAnchorLayoutFromEnd = in.readInt() == 1;
mLastLayoutRTL = in.readInt() == 1;
+ //noinspection unchecked
mFullSpanItems = in.readArrayList(
LazySpanLookup.FullSpanItem.class.getClassLoader());
}
diff --git a/v7/recyclerview/tests/AndroidManifest.xml b/v7/recyclerview/tests/AndroidManifest.xml
index 5047517..69ff69c 100644
--- a/v7/recyclerview/tests/AndroidManifest.xml
+++ b/v7/recyclerview/tests/AndroidManifest.xml
@@ -17,16 +17,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="android.support.v7.recyclerview.test">
- <uses-sdk android:minSdkVersion="7" tools:overrideLibrary="android.support.test,
- android.support.test.espresso, android.support.test.espresso.idling"/>
+ <uses-sdk android:minSdkVersion="7" tools:overrideLibrary="android.support.test,
+ android.app, android.support.test.rule, android.support.test.espresso,
+ android.support.test.espresso.idling"/>
<application>
- <uses-library android:name="android.test.runner" />
+ <uses-library android:name="android.test.runner"/>
<activity android:name="android.support.v7.widget.test.RecyclerViewTestActivity"/>
<activity android:name="android.support.v7.widget.TestActivity"/>
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="android.support.v7.recyclerview.test"
- />
+ />
</manifest>
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java b/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java
index f985898..8679043 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java
@@ -17,11 +17,13 @@
package android.support.v7.util;
import android.support.annotation.UiThread;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.SparseBooleanArray;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+@MediumTest
public class AsyncListUtilTest extends BaseThreadedTest {
private static final int TILE_SIZE = 10;
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/MessageQueueTest.java b/v7/recyclerview/tests/src/android/support/v7/util/MessageQueueTest.java
index 28c14d0..044f9d4 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/MessageQueueTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/MessageQueueTest.java
@@ -17,6 +17,7 @@
package android.support.v7.util;
import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
import org.junit.Before;
import org.junit.Test;
@@ -28,6 +29,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class MessageQueueTest {
MessageThreadUtil.MessageQueue mQueue;
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java b/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java
index 472374d..f5f32b8 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java
@@ -21,12 +21,14 @@
import android.os.Looper;
import android.support.annotation.UiThread;
+import android.test.suitebuilder.annotation.MediumTest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
+@MediumTest
public class ThreadUtilTest extends BaseThreadedTest {
Map<String, LockedObject> results = new HashMap<>();
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java b/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java
index 42ddc22..ada1694 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java
@@ -18,6 +18,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.support.v7.util.TileList;
+import android.test.suitebuilder.annotation.SmallTest;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.CoreMatchers.*;
@@ -26,6 +27,7 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class TileListTest {
int mTileSize = 3;
TileList<Integer> mTileList;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
index b8f6788..cc23c68 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.support.v7.util.AsyncListUtil;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -26,6 +27,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+@MediumTest
public class AsyncListUtilLayoutTest extends BaseRecyclerViewInstrumentationTest {
private static final boolean DEBUG = false;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
new file mode 100644
index 0000000..201532a
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.View;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+public class BaseLinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+ protected static final boolean DEBUG = false;
+ protected static final String TAG = "LinearLayoutManagerTest";
+
+ protected static List<Config> createBaseVariations() {
+ List<Config> variations = new ArrayList<>();
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (boolean stackFromBottom : new boolean[]{false, true}) {
+ variations.add(new Config(orientation, reverseLayout, stackFromBottom));
+ }
+ }
+ }
+ return variations;
+ }
+
+ WrappedLinearLayoutManager mLayoutManager;
+ TestAdapter mTestAdapter;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ protected static List<Config> addConfigVariation(List<Config> base, String fieldName,
+ Object... variations)
+ throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
+ List<Config> newConfigs = new ArrayList<Config>();
+ Field field = Config.class.getDeclaredField(fieldName);
+ for (Config config : base) {
+ for (Object variation : variations) {
+ Config newConfig = (Config) config.clone();
+ field.set(newConfig, variation);
+ newConfigs.add(newConfig);
+ }
+ }
+ return newConfigs;
+ }
+
+ void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
+ mRecyclerView = inflateWrappedRV();
+
+ mRecyclerView.setHasFixedSize(true);
+ mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
+ : config.mTestAdapter;
+ mRecyclerView.setAdapter(mTestAdapter);
+ mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
+ config.mReverseLayout);
+ mLayoutManager.setStackFromEnd(config.mStackFromEnd);
+ mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ if (waitForFirstLayout) {
+ waitForFirstLayout();
+ }
+ }
+
+ public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
+ throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, false), true);
+
+ mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
+ @Override
+ void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ if (state.isPreLayout()) {
+ assertEquals("pending scroll position should still be pending",
+ scrollPosition, mLayoutManager.mPendingScrollPosition);
+ if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+ assertEquals("pending scroll position offset should still be pending",
+ scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
+ }
+ } else {
+ RecyclerView.ViewHolder vh =
+ mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
+ assertNotNull("scroll to position should work", vh);
+ if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
+ assertEquals("scroll offset should be applied properly",
+ mLayoutManager.getPaddingTop() + scrollOffset +
+ ((RecyclerView.LayoutParams) vh.itemView
+ .getLayoutParams()).topMargin,
+ mLayoutManager.getDecoratedTop(vh.itemView));
+ }
+ }
+ }
+ };
+ mLayoutManager.expectLayouts(2);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ mTestAdapter.addAndNotify(0, 1);
+ if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
+ mLayoutManager.scrollToPosition(scrollPosition);
+ } else {
+ mLayoutManager.scrollToPositionWithOffset(scrollPosition,
+ scrollOffset);
+ }
+
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ }
+
+ protected void waitForFirstLayout() throws Throwable {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(mRecyclerView);
+ mLayoutManager.waitForLayout(2);
+ }
+
+ void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mLayoutManager.scrollToPositionWithOffset(position, offset);
+ }
+ });
+ }
+
+ public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
+ Map<Item, Rect> after, boolean strictItemEquality) {
+ Throwable throwable = null;
+ try {
+ assertRectSetsEqual("NOT " + message, before, after, strictItemEquality);
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ assertNotNull(message + "\ntwo layout should be different", throwable);
+ }
+
+ public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
+ assertRectSetsEqual(message, before, after, true);
+ }
+
+ public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
+ boolean strictItemEquality) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("checking rectangle equality.\n");
+ sb.append("before:\n");
+ for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+ sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
+ }
+ sb.append("after:\n");
+ for (Map.Entry<Item, Rect> entry : after.entrySet()) {
+ sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
+ }
+ message = message + "\n" + sb.toString();
+ assertEquals(message + ":\nitem counts should be equal", before.size()
+ , after.size());
+ for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+ final Item beforeItem = entry.getKey();
+ Rect afterRect = null;
+ if (strictItemEquality) {
+ afterRect = after.get(beforeItem);
+ assertNotNull(message + ":\nSame item should be visible after simple re-layout",
+ afterRect);
+ } else {
+ for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
+ final Item afterItem = afterEntry.getKey();
+ if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
+ afterRect = afterEntry.getValue();
+ break;
+ }
+ }
+ assertNotNull(message + ":\nItem with same adapter index should be visible " +
+ "after simple re-layout",
+ afterRect);
+ }
+ assertEquals(message + ":\nItem should be laid out at the same coordinates",
+ entry.getValue(), afterRect);
+ }
+ }
+
+ static class VisibleChildren {
+
+ int firstVisiblePosition = RecyclerView.NO_POSITION;
+
+ int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+ int lastVisiblePosition = RecyclerView.NO_POSITION;
+
+ int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
+
+ @Override
+ public String toString() {
+ return "VisibleChildren{" +
+ "firstVisiblePosition=" + firstVisiblePosition +
+ ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
+ ", lastVisiblePosition=" + lastVisiblePosition +
+ ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
+ '}';
+ }
+ }
+
+ static class OnLayoutListener {
+
+ void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+
+ void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+ }
+
+ static class Config implements Cloneable {
+
+ static final int DEFAULT_ITEM_COUNT = 100;
+
+ boolean mStackFromEnd;
+
+ int mOrientation = VERTICAL;
+
+ boolean mReverseLayout = false;
+
+ boolean mRecycleChildrenOnDetach = false;
+
+ int mItemCount = DEFAULT_ITEM_COUNT;
+
+ TestAdapter mTestAdapter;
+
+ Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
+ mOrientation = orientation;
+ mReverseLayout = reverseLayout;
+ mStackFromEnd = stackFromEnd;
+ }
+
+ public Config() {
+
+ }
+
+ Config adapter(TestAdapter adapter) {
+ mTestAdapter = adapter;
+ return this;
+ }
+
+ Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
+ mRecycleChildrenOnDetach = recycleChildrenOnDetach;
+ return this;
+ }
+
+ Config orientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ Config stackFromBottom(boolean stackFromBottom) {
+ mStackFromEnd = stackFromBottom;
+ return this;
+ }
+
+ Config reverseLayout(boolean reverseLayout) {
+ mReverseLayout = reverseLayout;
+ return this;
+ }
+
+ public Config itemCount(int itemCount) {
+ mItemCount = itemCount;
+ return this;
+ }
+
+ // required by convention
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public String toString() {
+ return "Config{" +
+ "mStackFromEnd=" + mStackFromEnd +
+ ", mOrientation=" + mOrientation +
+ ", mReverseLayout=" + mReverseLayout +
+ ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
+ ", mItemCount=" + mItemCount +
+ '}';
+ }
+ }
+
+ class WrappedLinearLayoutManager extends LinearLayoutManager {
+
+ CountDownLatch layoutLatch;
+
+ OrientationHelper mSecondaryOrientation;
+
+ OnLayoutListener mOnLayoutListener;
+
+ public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
+ super(context, orientation, reverseLayout);
+ }
+
+ public void expectLayouts(int count) {
+ layoutLatch = new CountDownLatch(count);
+ }
+
+ public void waitForLayout(long timeout) throws InterruptedException {
+ waitForLayout(timeout, TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void setOrientation(int orientation) {
+ super.setOrientation(orientation);
+ mSecondaryOrientation = null;
+ }
+
+ @Override
+ public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
+ if (DEBUG) {
+ Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
+ }
+ super.removeAndRecycleView(child, recycler);
+ }
+
+ @Override
+ public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
+ }
+ super.removeAndRecycleViewAt(index, recycler);
+ }
+
+ @Override
+ void ensureLayoutState() {
+ super.ensureLayoutState();
+ if (mSecondaryOrientation == null) {
+ mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
+ 1 - getOrientation());
+ }
+ }
+
+ private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
+ layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
+ assertEquals("all expected layouts should be executed at the expected time",
+ 0, layoutLatch.getCount());
+ getInstrumentation().waitForIdleSync();
+ }
+
+ @Override
+ LayoutState createLayoutState() {
+ return new LayoutState() {
+ @Override
+ View next(RecyclerView.Recycler recycler) {
+ final boolean hadMore = hasMore(mRecyclerView.mState);
+ final int position = mCurrentPosition;
+ View next = super.next(recycler);
+ assertEquals("if has more, should return a view", hadMore, next != null);
+ assertEquals("position of the returned view must match current position",
+ position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
+ return next;
+ }
+ };
+ }
+
+ public String getBoundsLog() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
+ .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
+ sb.append("\nchildren bounds\n");
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
+ .append("[").append("start:").append(
+ mOrientationHelper.getDecoratedStart(child)).append(", end:")
+ .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
+ }
+ return sb.toString();
+ }
+
+ public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
+ RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
+ if (itemAnimator == null) {
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(1);
+ final boolean running = itemAnimator.isRunning(
+ new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
+ @Override
+ public void onAnimationsFinished() {
+ latch.countDown();
+ }
+ }
+ );
+ if (running) {
+ latch.await(timeoutInSeconds, TimeUnit.SECONDS);
+ }
+ }
+
+ public VisibleChildren traverseAndFindVisibleChildren() {
+ int childCount = getChildCount();
+ final VisibleChildren visibleChildren = new VisibleChildren();
+ final int start = mOrientationHelper.getStartAfterPadding();
+ final int end = mOrientationHelper.getEndAfterPadding();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ final int childStart = mOrientationHelper.getDecoratedStart(child);
+ final int childEnd = mOrientationHelper.getDecoratedEnd(child);
+ final boolean fullyVisible = childStart >= start && childEnd <= end;
+ final boolean hidden = childEnd <= start || childStart >= end;
+ if (hidden) {
+ continue;
+ }
+ final int position = getPosition(child);
+ if (fullyVisible) {
+ if (position < visibleChildren.firstFullyVisiblePosition ||
+ visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
+ visibleChildren.firstFullyVisiblePosition = position;
+ }
+
+ if (position > visibleChildren.lastFullyVisiblePosition) {
+ visibleChildren.lastFullyVisiblePosition = position;
+ }
+ }
+
+ if (position < visibleChildren.firstVisiblePosition ||
+ visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
+ visibleChildren.firstVisiblePosition = position;
+ }
+
+ if (position > visibleChildren.lastVisiblePosition) {
+ visibleChildren.lastVisiblePosition = position;
+ }
+
+ }
+ return visibleChildren;
+ }
+
+ Rect getViewBounds(View view) {
+ if (getOrientation() == HORIZONTAL) {
+ return new Rect(
+ mOrientationHelper.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedStart(view),
+ mOrientationHelper.getDecoratedEnd(view),
+ mSecondaryOrientation.getDecoratedEnd(view));
+ } else {
+ return new Rect(
+ mSecondaryOrientation.getDecoratedStart(view),
+ mOrientationHelper.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedEnd(view),
+ mOrientationHelper.getDecoratedEnd(view));
+ }
+
+ }
+
+ Map<Item, Rect> collectChildCoordinates() throws Throwable {
+ final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int childCount = getChildCount();
+ Rect layoutBounds = new Rect(0, 0,
+ mLayoutManager.getWidth(), mLayoutManager.getHeight());
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
+ .getLayoutParams();
+ TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
+ Rect childBounds = getViewBounds(child);
+ if (new Rect(childBounds).intersect(layoutBounds)) {
+ items.put(vh.mBoundItem, childBounds);
+ }
+ }
+ }
+ });
+ return items;
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ if (mOnLayoutListener != null) {
+ mOnLayoutListener.before(recycler, state);
+ }
+ super.onLayoutChildren(recycler, state);
+ if (mOnLayoutListener != null) {
+ mOnLayoutListener.after(recycler, state);
+ }
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ layoutLatch.countDown();
+ }
+
+
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
index f45e485..3ea025a 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -105,6 +105,19 @@
});
}
+ public void focusSearch(final View focused, final int direction)
+ throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ View view = focused.focusSearch(direction);
+ if (view != null && view != focused) {
+ view.requestFocus();
+ }
+ }
+ });
+ }
+
protected WrappedRecyclerView inflateWrappedRV() {
return (WrappedRecyclerView)
LayoutInflater.from(getActivity()).inflate(R.layout.wrapped_test_rv,
@@ -223,12 +236,16 @@
public boolean requestFocus(final View view) {
final boolean[] result = new boolean[1];
- getActivity().runOnUiThread(new Runnable() {
- @Override
- public void run() {
- result[0] = view.requestFocus();
- }
- });
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ result[0] = view.requestFocus();
+ }
+ });
+ } catch (Throwable throwable) {
+ fail(throwable.getMessage());
+ }
return result[0];
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
index bbe2cf8..23e7527 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
@@ -17,12 +17,14 @@
package android.support.v7.widget;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
+@SmallTest
public class BucketTest extends AndroidTestCase {
ChildHelper.Bucket mBucket;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ChildHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ChildHelperTest.java
index 8163310..590fa98 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ChildHelperTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ChildHelperTest.java
@@ -24,6 +24,7 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
import android.test.mock.MockContext;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +32,7 @@
import java.util.List;
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class ChildHelperTest extends AndroidTestCase {
LoggingCallback mLoggingCallback;
ChildHelper mChildHelper;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
index 008354c..8e5ecfd 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -18,6 +18,7 @@
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -34,6 +35,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+@MediumTest
public class DefaultItemAnimatorTest extends ActivityInstrumentationTestCase2<TestActivity> {
private static final String TAG = "DefaultItemAnimatorTest";
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
index 6192c66..d5781fa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -17,12 +17,18 @@
package android.support.v7.widget;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.StateListDrawable;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.util.SparseIntArray;
+import android.util.StateSet;
+import android.view.FocusFinder;
import android.view.View;
import android.view.ViewGroup;
@@ -41,6 +47,7 @@
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
import static java.util.concurrent.TimeUnit.SECONDS;
+@MediumTest
public class GridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
static final String TAG = "GridLayoutManagerTest";
@@ -86,6 +93,65 @@
mGlm.waitForLayout(2);
}
+ public void testFocusSearchFailureUp() throws Throwable {
+ focusSearchFailure(false);
+ }
+
+ public void testFocusSearchFailureDown() throws Throwable {
+ focusSearchFailure(true);
+ }
+
+ public void focusSearchFailure(boolean scrollDown) throws Throwable {
+ final RecyclerView recyclerView = setupBasic(new Config(3, 31).reverseLayout(!scrollDown)
+ , new GridTestAdapter(31, 1) {
+ RecyclerView mAttachedRv;
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+ testViewHolder.itemView.setFocusable(true);
+ testViewHolder.itemView.setFocusableInTouchMode(true);
+ // Good to have colors for debugging
+ StateListDrawable stl = new StateListDrawable();
+ stl.addState(new int[]{android.R.attr.state_focused}, new ColorDrawable(Color.RED));
+ stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+ testViewHolder.itemView.setBackground(stl);
+ return testViewHolder;
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ mAttachedRv = recyclerView;
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3);
+ }
+ });
+ waitForFirstLayout(recyclerView);
+
+ View viewToFocus = recyclerView.findViewHolderForAdapterPosition(1).itemView;
+ assertTrue(requestFocus(viewToFocus));
+ getInstrumentation().waitForIdleSync();
+ assertSame(viewToFocus, recyclerView.getFocusedChild());
+ int pos = 1;
+ View focusedView = viewToFocus;
+ while (pos < 31) {
+ focusSearch(focusedView, scrollDown ? View.FOCUS_DOWN : View.FOCUS_UP);
+ getInstrumentation().waitForIdleSync();
+ while (recyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ Thread.sleep(100);
+ }
+ focusedView = recyclerView.getFocusedChild();
+ assertEquals(Math.min(pos + 3, mAdapter.getItemCount() - 1),
+ recyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ pos += 3;
+ }
+ }
+
@UiThreadTest
public void testScrollWithoutLayout() throws Throwable {
final RecyclerView recyclerView = setupBasic(new Config(3, 100));
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
index 116a80d..57bd26c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
@@ -17,6 +17,7 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.test.suitebuilder.annotation.MediumTest;
import java.util.ArrayList;
import java.util.HashMap;
@@ -31,6 +32,7 @@
/**
* Includes tests for the new RecyclerView animations API (v2).
*/
+@MediumTest
public class ItemAnimatorV2ApiTest extends BaseRecyclerViewAnimationsTest {
@Override
protected RecyclerView.ItemAnimator createItemAnimator() {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
new file mode 100644
index 0000000..27f420a
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2015 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Rect;
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.view.View;
+
+import java.util.List;
+import java.util.Map;
+
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+
+/**
+ * Tests that rely on the basic configuration and does not do any additions / removals
+ */
+@RunWith(Parameterized.class)
+public class LinearLayoutManagerBaseConfigSetTest extends BaseLinearLayoutManagerTest {
+
+ private final Config mConfig;
+
+ public LinearLayoutManagerBaseConfigSetTest(Config config) {
+ mConfig = config;
+ }
+
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Config> configs() throws CloneNotSupportedException {
+ return createBaseVariations();
+ }
+
+ @Test
+ @MediumTest
+ public void scrollToPositionWithOffsetTest() throws Throwable {
+ Config config = ((Config) mConfig.clone()).itemCount(300);
+ OrientationHelper orientationHelper = OrientationHelper
+ .createOrientationHelper(mLayoutManager, config.mOrientation);
+ Rect layoutBounds = getDecoratedRecyclerViewBounds();
+ // try scrolling towards head, should not affect anything
+ Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+ if (config.mStackFromEnd) {
+ scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
+ mLayoutManager.mOrientationHelper.getEnd() - 500);
+ } else {
+ scrollToPositionWithOffset(0, 20);
+ }
+ assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
+ before, mLayoutManager.collectChildCoordinates());
+ // try offsetting some visible children
+ int testCount = 10;
+ while (testCount-- > 0) {
+ // get middle child
+ final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
+ final int position = mRecyclerView.getChildLayoutPosition(child);
+ final int startOffset = config.mReverseLayout ?
+ orientationHelper.getEndAfterPadding() - orientationHelper
+ .getDecoratedEnd(child)
+ : orientationHelper.getDecoratedStart(child) - orientationHelper
+ .getStartAfterPadding();
+ final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
+ : startOffset / 2;
+ mLayoutManager.expectLayouts(1);
+ scrollToPositionWithOffset(position, scrollOffset);
+ mLayoutManager.waitForLayout(2);
+ final int finalOffset = config.mReverseLayout ?
+ orientationHelper.getEndAfterPadding() - orientationHelper
+ .getDecoratedEnd(child)
+ : orientationHelper.getDecoratedStart(child) - orientationHelper
+ .getStartAfterPadding();
+ assertEquals(config + " scroll with offset on a visible child should work fine " +
+ " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
+ + "child " + position,
+ scrollOffset, finalOffset);
+ }
+
+ // try scrolling to invisible children
+ testCount = 10;
+ // we test above and below, one by one
+ int offsetMultiplier = -1;
+ while (testCount-- > 0) {
+ final TargetTuple target = findInvisibleTarget(config);
+ final String logPrefix = config + " " + target;
+ mLayoutManager.expectLayouts(1);
+ final int offset = offsetMultiplier
+ * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
+ scrollToPositionWithOffset(target.mPosition, offset);
+ mLayoutManager.waitForLayout(2);
+ final View child = mLayoutManager.findViewByPosition(target.mPosition);
+ assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
+ + " should layout it", child);
+ final Rect bounds = mLayoutManager.getViewBounds(child);
+ if (DEBUG) {
+ Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
+ + layoutBounds + " with offset " + offset);
+ }
+
+ if (config.mReverseLayout) {
+ assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
+ + "layout, its end should align with recycler view's end - offset",
+ orientationHelper.getEndAfterPadding() - offset,
+ orientationHelper.getDecoratedEnd(child)
+ );
+ } else {
+ assertEquals(
+ logPrefix + " when scrolling with offset to an invisible child in normal"
+ + " layout its start should align with recycler view's start + "
+ + "offset",
+ orientationHelper.getStartAfterPadding() + offset,
+ orientationHelper.getDecoratedStart(child)
+ );
+ }
+ offsetMultiplier *= -1;
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void getFirstLastChildrenTest() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(300);
+ setupByConfig(config, true);
+ Runnable viewInBoundsTest = new Runnable() {
+ @Override
+ public void run() {
+ VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
+ final String boundsLog = mLayoutManager.getBoundsLog();
+ assertEquals(config + ":\nfirst visible child should match traversal result\n"
+ + boundsLog, visibleChildren.firstVisiblePosition,
+ mLayoutManager.findFirstVisibleItemPosition()
+ );
+ assertEquals(
+ config + ":\nfirst fully visible child should match traversal result\n"
+ + boundsLog, visibleChildren.firstFullyVisiblePosition,
+ mLayoutManager.findFirstCompletelyVisibleItemPosition()
+ );
+
+ assertEquals(config + ":\nlast visible child should match traversal result\n"
+ + boundsLog, visibleChildren.lastVisiblePosition,
+ mLayoutManager.findLastVisibleItemPosition()
+ );
+ assertEquals(
+ config + ":\nlast fully visible child should match traversal result\n"
+ + boundsLog, visibleChildren.lastFullyVisiblePosition,
+ mLayoutManager.findLastCompletelyVisibleItemPosition()
+ );
+ }
+ };
+ runTestOnUiThread(viewInBoundsTest);
+ // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+ // case
+ final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.smoothScrollToPosition(scrollPosition);
+ }
+ });
+ while (mLayoutManager.isSmoothScrolling() ||
+ mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ runTestOnUiThread(viewInBoundsTest);
+ Thread.sleep(400);
+ }
+ // delete all items
+ mLayoutManager.expectLayouts(2);
+ mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
+ mLayoutManager.waitForLayout(2);
+ // test empty case
+ runTestOnUiThread(viewInBoundsTest);
+ // set a new adapter with huge items to test full bounds check
+ mLayoutManager.expectLayouts(1);
+ final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
+ final TestAdapter newAdapter = new TestAdapter(100) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (config.mOrientation == HORIZONTAL) {
+ holder.itemView.setMinimumWidth(totalSpace + 5);
+ } else {
+ holder.itemView.setMinimumHeight(totalSpace + 5);
+ }
+ }
+ };
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.setAdapter(newAdapter);
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ runTestOnUiThread(viewInBoundsTest);
+ }
+
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ private TargetTuple findInvisibleTarget(Config config) {
+ int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
+ for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+ View child = mLayoutManager.getChildAt(i);
+ int position = mRecyclerView.getChildLayoutPosition(child);
+ if (position < minPosition) {
+ minPosition = position;
+ }
+ if (position > maxPosition) {
+ maxPosition = position;
+ }
+ }
+ final int tailTarget = maxPosition +
+ (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
+ final int headTarget = minPosition / 2;
+ final int target;
+ // where will the child come from ?
+ final int itemLayoutDirection;
+ if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
+ target = tailTarget;
+ itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
+ } else {
+ target = headTarget;
+ itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
+ }
+ if (DEBUG) {
+ Log.d(TAG,
+ config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
+ }
+ return new TargetTuple(target, itemLayoutDirection);
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
new file mode 100644
index 0000000..2027359
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+
+@RunWith(Parameterized.class)
+public class LinearLayoutManagerPrepareForDropTest extends BaseLinearLayoutManagerTest {
+
+ final BaseLinearLayoutManagerTest.Config mConfig;
+ final SelectTargetChildren mSelectTargetChildren;
+
+ public LinearLayoutManagerPrepareForDropTest(
+ Config config, SelectTargetChildren selectTargetChildren) {
+ mConfig = config;
+ mSelectTargetChildren = selectTargetChildren;
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Parameterized.Parameters(name = "{0}_{1}")
+ public static Iterable<Object[]> params() {
+ SelectTargetChildren[] selectors
+ = new SelectTargetChildren[]{
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{1, 0};
+ }
+ },
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{0, 1};
+ }
+ },
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{childCount - 1, childCount - 2};
+ }
+ },
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{childCount - 2, childCount - 1};
+ }
+ },
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{childCount / 2, childCount / 2 + 1};
+ }
+ },
+ new SelectTargetChildren() {
+ @Override
+ public int[] selectTargetChildren(int childCount) {
+ return new int[]{childCount / 2 + 1, childCount / 2};
+ }
+ }
+ };
+ List<Object[]> variations = new ArrayList<>();
+ for (SelectTargetChildren selector : selectors) {
+ for (BaseLinearLayoutManagerTest.Config config : createBaseVariations()) {
+ variations.add(new Object[]{config, selector});
+ }
+ }
+ return variations;
+ }
+
+ @Test
+ @MediumTest
+ public void prepareForDropTest()
+ throws Throwable {
+ final Config config = (Config) mConfig.clone();
+ config.mTestAdapter = new BaseRecyclerViewInstrumentationTest.TestAdapter(100) {
+ @Override
+ public void onBindViewHolder(BaseRecyclerViewInstrumentationTest.TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (config.mOrientation == HORIZONTAL) {
+ final int base = mRecyclerView.getWidth() / 5;
+ final int itemRand = holder.mBoundItem.mText.hashCode() % base;
+ holder.itemView.setMinimumWidth(base + itemRand);
+ } else {
+ final int base = mRecyclerView.getHeight() / 5;
+ final int itemRand = holder.mBoundItem.mText.hashCode() % base;
+ holder.itemView.setMinimumHeight(base + itemRand);
+ }
+ }
+ };
+ setupByConfig(config, true);
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(mTestAdapter.getItemCount() / 2);
+ mLayoutManager.waitForLayout(1);
+ int[] positions = mSelectTargetChildren.selectTargetChildren(mRecyclerView.getChildCount());
+ final View fromChild = mLayoutManager.getChildAt(positions[0]);
+ final int fromPos = mLayoutManager.getPosition(fromChild);
+ final View onChild = mLayoutManager.getChildAt(positions[1]);
+ final int toPos = mLayoutManager.getPosition(onChild);
+ final OrientationHelper helper = mLayoutManager.mOrientationHelper;
+ final int dragCoordinate;
+ final boolean towardsHead = toPos < fromPos;
+ final int referenceLine;
+ if (config.mReverseLayout == towardsHead) {
+ referenceLine = helper.getDecoratedEnd(onChild);
+ dragCoordinate = referenceLine + 3 -
+ helper.getDecoratedMeasurement(fromChild);
+ } else {
+ referenceLine = helper.getDecoratedStart(onChild);
+ dragCoordinate = referenceLine - 3;
+ }
+ mLayoutManager.expectLayouts(2);
+
+ final int x, y;
+ if (config.mOrientation == HORIZONTAL) {
+ x = dragCoordinate;
+ y = fromChild.getTop();
+ } else {
+ y = dragCoordinate;
+ x = fromChild.getLeft();
+ }
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mTestAdapter.moveInUIThread(fromPos, toPos);
+ mTestAdapter.notifyItemMoved(fromPos, toPos);
+ mLayoutManager.prepareForDrop(fromChild, onChild, x, y);
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+
+ assertSame(fromChild, mRecyclerView.findViewHolderForAdapterPosition(toPos).itemView);
+ // make sure it has the position we wanted
+ if (config.mReverseLayout == towardsHead) {
+ assertEquals(referenceLine, helper.getDecoratedEnd(fromChild));
+ } else {
+ assertEquals(referenceLine, helper.getDecoratedStart(fromChild));
+ }
+ }
+
+ protected interface SelectTargetChildren {
+
+ int[] selectTargetChildren(int childCount);
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerResizeTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerResizeTest.java
new file mode 100644
index 0000000..2a5a377
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerResizeTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2015 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.FrameLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+
+@RunWith(Parameterized.class)
+public class LinearLayoutManagerResizeTest extends BaseLinearLayoutManagerTest {
+
+ final Config mConfig;
+
+ public LinearLayoutManagerResizeTest(Config config) {
+ mConfig = config;
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Config> testResize() throws Throwable {
+ List<Config> configs = new ArrayList<>();
+ for (Config config : addConfigVariation(createBaseVariations(), "mItemCount", 5
+ , Config.DEFAULT_ITEM_COUNT)) {
+ configs.add(config);
+ }
+ return configs;
+ }
+
+ @MediumTest
+ @Test
+ public void resize() throws Throwable {
+ final Config config = (Config) mConfig.clone();
+ final FrameLayout container = getRecyclerViewContainer();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ container.setPadding(0, 0, 0, 0);
+ }
+ });
+
+ setupByConfig(config, true);
+ int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
+ int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
+ int lastCompletelyVisibleItemPosition = mLayoutManager
+ .findLastCompletelyVisibleItemPosition();
+ int firstCompletelyVisibleItemPosition = mLayoutManager
+ .findFirstCompletelyVisibleItemPosition();
+ mLayoutManager.expectLayouts(1);
+ // resize the recycler view to half
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (config.mOrientation == HORIZONTAL) {
+ container.setPadding(0, 0, container.getWidth() / 2, 0);
+ } else {
+ container.setPadding(0, 0, 0, container.getWidth() / 2);
+ }
+ }
+ });
+ mLayoutManager.waitForLayout(1);
+ if (config.mStackFromEnd) {
+ assertEquals("[" + config + "]: last visible position should not change.",
+ lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
+ assertEquals("[" + config + "]: last completely visible position should not change",
+ lastCompletelyVisibleItemPosition,
+ mLayoutManager.findLastCompletelyVisibleItemPosition());
+ } else {
+ assertEquals("[" + config + "]: first visible position should not change.",
+ firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
+ assertEquals("[" + config + "]: last completely visible position should not change",
+ firstCompletelyVisibleItemPosition,
+ mLayoutManager.findFirstCompletelyVisibleItemPosition());
+ }
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSavedStateTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSavedStateTest.java
new file mode 100644
index 0000000..bd157b8
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSavedStateTest.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2015 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+@RunWith(Parameterized.class)
+public class LinearLayoutManagerSavedStateTest extends BaseLinearLayoutManagerTest {
+ final Config mConfig;
+ final boolean mWaitForLayout;
+ final boolean mLoadDataAfterRestore;
+ final PostLayoutRunnable mPostLayoutOperation;
+ final PostRestoreRunnable mPostRestoreOperation;
+
+ public LinearLayoutManagerSavedStateTest(Config config, boolean waitForLayout,
+ boolean loadDataAfterRestore, PostLayoutRunnable postLayoutOperation,
+ PostRestoreRunnable postRestoreOperation) {
+ mConfig = config;
+ mWaitForLayout = waitForLayout;
+ mLoadDataAfterRestore = loadDataAfterRestore;
+ mPostLayoutOperation = postLayoutOperation;
+ mPostRestoreOperation = postRestoreOperation;
+ mPostLayoutOperation.mLayoutManagerDelegate = new Delegate<WrappedLinearLayoutManager>() {
+ @Override
+ public WrappedLinearLayoutManager get() {
+ return mLayoutManager;
+ }
+ };
+ mPostLayoutOperation.mTestAdapterDelegate = new Delegate<TestAdapter>() {
+ @Override
+ public TestAdapter get() {
+ return mTestAdapter;
+ }
+ };
+ mPostRestoreOperation.mLayoutManagerDelegate = new Delegate<WrappedLinearLayoutManager>() {
+ @Override
+ public WrappedLinearLayoutManager get() {
+ return mLayoutManager;
+ }
+ };
+ mPostRestoreOperation.mTestAdapterDelegate = new Delegate<TestAdapter>() {
+ @Override
+ public TestAdapter get() {
+ return mTestAdapter;
+ }
+ };
+ }
+
+ @Parameterized.Parameters(name = "{0}_waitForLayout:{1}_loadDataAfterRestore:{2}"
+ + "_postLayout:{3}_postRestore:{4}")
+ public static Iterable<Object[]> params()
+ throws IllegalAccessException, CloneNotSupportedException, NoSuchFieldException {
+ PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ // do nothing
+ }
+
+ @Override
+ public String describe() {
+ return "doing nothing";
+ }
+ },
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ layoutManager().expectLayouts(1);
+ scrollToPosition(testAdapter().getItemCount() * 3 / 4);
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position";
+ }
+ },
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ layoutManager().expectLayouts(1);
+ scrollToPositionWithOffset(testAdapter().getItemCount() / 3,
+ 50);
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position with positive offset";
+ }
+ },
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ layoutManager().expectLayouts(1);
+ scrollToPositionWithOffset(testAdapter().getItemCount() * 2 / 3,
+ -10); // Some tests break if this value is below the item height.
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position with negative offset";
+ }
+ }
+ };
+
+ PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
+ new PostRestoreRunnable() {
+ @Override
+ public String describe() {
+ return "Doing nothing";
+ }
+ },
+ new PostRestoreRunnable() {
+ @Override
+ void onAfterRestore(Config config) throws Throwable {
+ // update config as well so that restore assertions will work
+ config.mOrientation = 1 - config.mOrientation;
+ layoutManager().setOrientation(config.mOrientation);
+ }
+
+ @Override
+ boolean shouldLayoutMatch(Config config) {
+ return config.mItemCount == 0;
+ }
+
+ @Override
+ public String describe() {
+ return "Changing orientation";
+ }
+ },
+ new PostRestoreRunnable() {
+ @Override
+ void onAfterRestore(Config config) throws Throwable {
+ config.mStackFromEnd = !config.mStackFromEnd;
+ layoutManager().setStackFromEnd(config.mStackFromEnd);
+ }
+
+ @Override
+ boolean shouldLayoutMatch(Config config) {
+ return true; //stack from end should not move items on change
+ }
+
+ @Override
+ public String describe() {
+ return "Changing stack from end";
+ }
+ },
+ new PostRestoreRunnable() {
+ @Override
+ void onAfterRestore(Config config) throws Throwable {
+ config.mReverseLayout = !config.mReverseLayout;
+ layoutManager().setReverseLayout(config.mReverseLayout);
+ }
+
+ @Override
+ boolean shouldLayoutMatch(Config config) {
+ return config.mItemCount == 0;
+ }
+
+ @Override
+ public String describe() {
+ return "Changing reverse layout";
+ }
+ },
+ new PostRestoreRunnable() {
+ @Override
+ void onAfterRestore(Config config) throws Throwable {
+ config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
+ layoutManager().setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
+ }
+
+ @Override
+ boolean shouldLayoutMatch(Config config) {
+ return true;
+ }
+
+ @Override
+ String describe() {
+ return "Change should recycle children";
+ }
+ },
+ new PostRestoreRunnable() {
+ int position;
+ @Override
+ void onAfterRestore(Config config) throws Throwable {
+ position = testAdapter().getItemCount() / 2;
+ layoutManager().scrollToPosition(position);
+ }
+
+ @Override
+ boolean shouldLayoutMatch(Config config) {
+ return testAdapter().getItemCount() == 0;
+ }
+
+ @Override
+ String describe() {
+ return "Scroll to position " + position ;
+ }
+
+ @Override
+ void onAfterReLayout(Config config) {
+ if (testAdapter().getItemCount() > 0) {
+ assertEquals(config + ":scrolled view should be last completely visible",
+ position,
+ config.mStackFromEnd ?
+ layoutManager().findLastCompletelyVisibleItemPosition()
+ : layoutManager().findFirstCompletelyVisibleItemPosition());
+ }
+ }
+ }
+ };
+ boolean[] waitForLayoutOptions = new boolean[]{true, false};
+ boolean[] loadDataAfterRestoreOptions = new boolean[]{true, false};
+ List<Config> variations = addConfigVariation(createBaseVariations(), "mItemCount", 0, 300);
+ variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
+
+ List<Object[]> params = new ArrayList<>();
+ for (Config config : variations) {
+ for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
+ for (boolean waitForLayout : waitForLayoutOptions) {
+ for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
+ for (boolean loadDataAfterRestore : loadDataAfterRestoreOptions) {
+ params.add(new Object[]{
+ config.clone(), waitForLayout,
+ loadDataAfterRestore, postLayoutRunnable, postRestoreRunnable
+ });
+ }
+ }
+
+ }
+ }
+ }
+ return params;
+ }
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ injectInstrumentation(InstrumentationRegistry.getInstrumentation());
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ @MediumTest
+ public void savedStateTest()
+ throws Throwable {
+ if (DEBUG) {
+ Log.d(TAG, "testing saved state with wait for layout = " + mWaitForLayout + " config " +
+ mConfig + " post layout action " + mPostLayoutOperation.describe() +
+ "post restore action " + mPostRestoreOperation.describe());
+ }
+ setupByConfig(mConfig, false);
+
+ if (mWaitForLayout) {
+ waitForFirstLayout();
+ mPostLayoutOperation.run();
+ }
+ Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+ Parcelable savedState = mRecyclerView.onSaveInstanceState();
+ // we append a suffix to the parcelable to test out of bounds
+ String parcelSuffix = UUID.randomUUID().toString();
+ Parcel parcel = Parcel.obtain();
+ savedState.writeToParcel(parcel, 0);
+ parcel.writeString(parcelSuffix);
+ removeRecyclerView();
+ // reset for reading
+ parcel.setDataPosition(0);
+ // re-create
+ savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
+ removeRecyclerView();
+
+ final int itemCount = mTestAdapter.getItemCount();
+ if (mLoadDataAfterRestore) {
+ mTestAdapter.deleteAndNotify(0, itemCount);
+ }
+
+ RecyclerView restored = new RecyclerView(getActivity());
+ // this config should be no op.
+ mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
+ mConfig.mOrientation, mConfig.mReverseLayout);
+ mLayoutManager.setStackFromEnd(mConfig.mStackFromEnd);
+ restored.setLayoutManager(mLayoutManager);
+ // use the same adapter for Rect matching
+ restored.setAdapter(mTestAdapter);
+ restored.onRestoreInstanceState(savedState);
+
+ if (mLoadDataAfterRestore) {
+ mTestAdapter.addAndNotify(itemCount);
+ }
+
+ mPostRestoreOperation.onAfterRestore(mConfig);
+ assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+ parcel.readString());
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ mLayoutManager.waitForLayout(2);
+ // calculate prefix here instead of above to include post restore changes
+ final String logPrefix = mConfig + "\npostLayout:" + mPostLayoutOperation.describe() +
+ "\npostRestore:" + mPostRestoreOperation.describe() + "\n";
+ assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
+ mConfig.mReverseLayout, mLayoutManager.getReverseLayout());
+ assertEquals(logPrefix + " on saved state, orientation should be preserved",
+ mConfig.mOrientation, mLayoutManager.getOrientation());
+ assertEquals(logPrefix + " on saved state, stack from end should be preserved",
+ mConfig.mStackFromEnd, mLayoutManager.getStackFromEnd());
+ if (mWaitForLayout) {
+ final boolean strictItemEquality = !mLoadDataAfterRestore;
+ if (mPostRestoreOperation.shouldLayoutMatch(mConfig)) {
+ assertRectSetsEqual(
+ logPrefix + ": on restore, previous view positions should be preserved",
+ before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
+ } else {
+ assertRectSetsNotEqual(
+ logPrefix
+ + ": on restore with changes, previous view positions should NOT "
+ + "be preserved",
+ before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
+ }
+ mPostRestoreOperation.onAfterReLayout(mConfig);
+ }
+ }
+
+ protected static abstract class PostLayoutRunnable {
+ private Delegate<WrappedLinearLayoutManager> mLayoutManagerDelegate;
+ private Delegate<TestAdapter> mTestAdapterDelegate;
+ protected WrappedLinearLayoutManager layoutManager() {
+ return mLayoutManagerDelegate.get();
+ }
+ protected TestAdapter testAdapter() {
+ return mTestAdapterDelegate.get();
+ }
+
+ abstract void run() throws Throwable;
+ void scrollToPosition(final int position) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ layoutManager().scrollToPosition(position);
+ }
+ });
+ }
+ void scrollToPositionWithOffset(final int position, final int offset) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ layoutManager().scrollToPositionWithOffset(position, offset);
+ }
+ });
+ }
+ abstract String describe();
+
+ @Override
+ public String toString() {
+ return describe();
+ }
+ }
+
+ protected static abstract class PostRestoreRunnable {
+ private Delegate<WrappedLinearLayoutManager> mLayoutManagerDelegate;
+ private Delegate<TestAdapter> mTestAdapterDelegate;
+ protected WrappedLinearLayoutManager layoutManager() {
+ return mLayoutManagerDelegate.get();
+ }
+ protected TestAdapter testAdapter() {
+ return mTestAdapterDelegate.get();
+ }
+
+ void onAfterRestore(Config config) throws Throwable {
+ }
+
+ abstract String describe();
+
+ boolean shouldLayoutMatch(Config config) {
+ return true;
+ }
+
+ void onAfterReLayout(Config config) {
+
+ };
+
+ @Override
+ public String toString() {
+ return describe();
+ }
+ }
+
+ private interface Delegate<T> {
+ T get();
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
index c59d550..35e1fc8 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -23,6 +23,7 @@
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -50,61 +51,8 @@
* and stability of LinearLayoutManager in response to different events (state change, scrolling
* etc) where it is very hard to do manual testing.
*/
-public class LinearLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
-
- private static final boolean DEBUG = false;
-
- private static final String TAG = "LinearLayoutManagerTest";
-
- WrappedLinearLayoutManager mLayoutManager;
-
- TestAdapter mTestAdapter;
-
- final List<Config> mBaseVariations = new ArrayList<Config>();
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
- for (boolean reverseLayout : new boolean[]{false, true}) {
- for (boolean stackFromBottom : new boolean[]{false, true}) {
- mBaseVariations.add(new Config(orientation, reverseLayout, stackFromBottom));
- }
- }
- }
- }
-
- protected List<Config> addConfigVariation(List<Config> base, String fieldName,
- Object... variations)
- throws CloneNotSupportedException, NoSuchFieldException, IllegalAccessException {
- List<Config> newConfigs = new ArrayList<Config>();
- Field field = Config.class.getDeclaredField(fieldName);
- for (Config config : base) {
- for (Object variation : variations) {
- Config newConfig = (Config) config.clone();
- field.set(newConfig, variation);
- newConfigs.add(newConfig);
- }
- }
- return newConfigs;
- }
-
- void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
- mRecyclerView = inflateWrappedRV();
-
- mRecyclerView.setHasFixedSize(true);
- mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
- : config.mTestAdapter;
- mRecyclerView.setAdapter(mTestAdapter);
- mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
- config.mReverseLayout);
- mLayoutManager.setStackFromEnd(config.mStackFromEnd);
- mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
- mRecyclerView.setLayoutManager(mLayoutManager);
- if (waitForFirstLayout) {
- waitForFirstLayout();
- }
- }
+@MediumTest
+public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
public void testRemoveAnchorItem() throws Throwable {
removeAnchorItemTest(
@@ -401,176 +349,6 @@
}
}
- public void testResize() throws Throwable {
- for(Config config : addConfigVariation(mBaseVariations, "mItemCount", 5
- , Config.DEFAULT_ITEM_COUNT)) {
- stackFromEndTest(config);
- removeRecyclerView();
- }
- }
-
- public void testScrollToPositionWithOffset() throws Throwable {
- for (Config config : mBaseVariations) {
- scrollToPositionWithOffsetTest(config.itemCount(300));
- removeRecyclerView();
- }
- }
-
- public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
- setupByConfig(config, true);
- OrientationHelper orientationHelper = OrientationHelper
- .createOrientationHelper(mLayoutManager, config.mOrientation);
- Rect layoutBounds = getDecoratedRecyclerViewBounds();
- // try scrolling towards head, should not affect anything
- Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
- if (config.mStackFromEnd) {
- scrollToPositionWithOffset(mTestAdapter.getItemCount() - 1,
- mLayoutManager.mOrientationHelper.getEnd() - 500);
- } else {
- scrollToPositionWithOffset(0, 20);
- }
- assertRectSetsEqual(config + " trying to over scroll with offset should be no-op",
- before, mLayoutManager.collectChildCoordinates());
- // try offsetting some visible children
- int testCount = 10;
- while (testCount-- > 0) {
- // get middle child
- final View child = mLayoutManager.getChildAt(mLayoutManager.getChildCount() / 2);
- final int position = mRecyclerView.getChildLayoutPosition(child);
- final int startOffset = config.mReverseLayout ?
- orientationHelper.getEndAfterPadding() - orientationHelper
- .getDecoratedEnd(child)
- : orientationHelper.getDecoratedStart(child) - orientationHelper
- .getStartAfterPadding();
- final int scrollOffset = config.mStackFromEnd ? startOffset + startOffset / 2
- : startOffset / 2;
- mLayoutManager.expectLayouts(1);
- scrollToPositionWithOffset(position, scrollOffset);
- mLayoutManager.waitForLayout(2);
- final int finalOffset = config.mReverseLayout ?
- orientationHelper.getEndAfterPadding() - orientationHelper
- .getDecoratedEnd(child)
- : orientationHelper.getDecoratedStart(child) - orientationHelper
- .getStartAfterPadding();
- assertEquals(config + " scroll with offset on a visible child should work fine " +
- " offset:" + finalOffset + " , existing offset:" + startOffset + ", "
- + "child " + position,
- scrollOffset, finalOffset);
- }
-
- // try scrolling to invisible children
- testCount = 10;
- // we test above and below, one by one
- int offsetMultiplier = -1;
- while (testCount-- > 0) {
- final TargetTuple target = findInvisibleTarget(config);
- final String logPrefix = config + " " + target;
- mLayoutManager.expectLayouts(1);
- final int offset = offsetMultiplier
- * orientationHelper.getDecoratedMeasurement(mLayoutManager.getChildAt(0)) / 3;
- scrollToPositionWithOffset(target.mPosition, offset);
- mLayoutManager.waitForLayout(2);
- final View child = mLayoutManager.findViewByPosition(target.mPosition);
- assertNotNull(logPrefix + " scrolling to a mPosition with offset " + offset
- + " should layout it", child);
- final Rect bounds = mLayoutManager.getViewBounds(child);
- if (DEBUG) {
- Log.d(TAG, logPrefix + " post scroll to invisible mPosition " + bounds + " in "
- + layoutBounds + " with offset " + offset);
- }
-
- if (config.mReverseLayout) {
- assertEquals(logPrefix + " when scrolling with offset to an invisible in reverse "
- + "layout, its end should align with recycler view's end - offset",
- orientationHelper.getEndAfterPadding() - offset,
- orientationHelper.getDecoratedEnd(child)
- );
- } else {
- assertEquals(logPrefix + " when scrolling with offset to an invisible child in normal"
- + " layout its start should align with recycler view's start + "
- + "offset",
- orientationHelper.getStartAfterPadding() + offset,
- orientationHelper.getDecoratedStart(child)
- );
- }
- offsetMultiplier *= -1;
- }
- }
-
- private TargetTuple findInvisibleTarget(Config config) {
- int minPosition = Integer.MAX_VALUE, maxPosition = Integer.MIN_VALUE;
- for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
- View child = mLayoutManager.getChildAt(i);
- int position = mRecyclerView.getChildLayoutPosition(child);
- if (position < minPosition) {
- minPosition = position;
- }
- if (position > maxPosition) {
- maxPosition = position;
- }
- }
- final int tailTarget = maxPosition +
- (mRecyclerView.getAdapter().getItemCount() - maxPosition) / 2;
- final int headTarget = minPosition / 2;
- final int target;
- // where will the child come from ?
- final int itemLayoutDirection;
- if (Math.abs(tailTarget - maxPosition) > Math.abs(headTarget - minPosition)) {
- target = tailTarget;
- itemLayoutDirection = config.mReverseLayout ? LAYOUT_START : LAYOUT_END;
- } else {
- target = headTarget;
- itemLayoutDirection = config.mReverseLayout ? LAYOUT_END : LAYOUT_START;
- }
- if (DEBUG) {
- Log.d(TAG,
- config + " target:" + target + " min:" + minPosition + ", max:" + maxPosition);
- }
- return new TargetTuple(target, itemLayoutDirection);
- }
-
- public void stackFromEndTest(final Config config) throws Throwable {
- final FrameLayout container = getRecyclerViewContainer();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- container.setPadding(0, 0, 0, 0);
- }
- });
-
- setupByConfig(config, true);
- int lastVisibleItemPosition = mLayoutManager.findLastVisibleItemPosition();
- int firstVisibleItemPosition = mLayoutManager.findFirstVisibleItemPosition();
- int lastCompletelyVisibleItemPosition = mLayoutManager.findLastCompletelyVisibleItemPosition();
- int firstCompletelyVisibleItemPosition = mLayoutManager.findFirstCompletelyVisibleItemPosition();
- mLayoutManager.expectLayouts(1);
- // resize the recycler view to half
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- if (config.mOrientation == HORIZONTAL) {
- container.setPadding(0, 0, container.getWidth() / 2, 0);
- } else {
- container.setPadding(0, 0, 0, container.getWidth() / 2);
- }
- }
- });
- mLayoutManager.waitForLayout(1);
- if (config.mStackFromEnd) {
- assertEquals("[" + config + "]: last visible position should not change.",
- lastVisibleItemPosition, mLayoutManager.findLastVisibleItemPosition());
- assertEquals("[" + config + "]: last completely visible position should not change",
- lastCompletelyVisibleItemPosition,
- mLayoutManager.findLastCompletelyVisibleItemPosition());
- } else {
- assertEquals("[" + config + "]: first visible position should not change.",
- firstVisibleItemPosition, mLayoutManager.findFirstVisibleItemPosition());
- assertEquals("[" + config + "]: last completely visible position should not change",
- firstCompletelyVisibleItemPosition,
- mLayoutManager.findFirstCompletelyVisibleItemPosition());
- }
- }
-
public void testScrollToPositionWithPredictive() throws Throwable {
scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
@@ -582,63 +360,6 @@
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2, 10);
}
- public void scrollToPositionWithPredictive(final int scrollPosition, final int scrollOffset)
- throws Throwable {
- setupByConfig(new Config(VERTICAL, false, false), true);
-
- mLayoutManager.mOnLayoutListener = new OnLayoutListener() {
- @Override
- void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
- if (state.isPreLayout()) {
- assertEquals("pending scroll position should still be pending",
- scrollPosition, mLayoutManager.mPendingScrollPosition);
- if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
- assertEquals("pending scroll position offset should still be pending",
- scrollOffset, mLayoutManager.mPendingScrollPositionOffset);
- }
- } else {
- RecyclerView.ViewHolder vh =
- mRecyclerView.findViewHolderForLayoutPosition(scrollPosition);
- assertNotNull("scroll to position should work", vh);
- if (scrollOffset != LinearLayoutManager.INVALID_OFFSET) {
- assertEquals("scroll offset should be applied properly",
- mLayoutManager.getPaddingTop() + scrollOffset +
- ((RecyclerView.LayoutParams) vh.itemView
- .getLayoutParams()).topMargin,
- mLayoutManager.getDecoratedTop(vh.itemView));
- }
- }
- }
- };
- mLayoutManager.expectLayouts(2);
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- mTestAdapter.addAndNotify(0, 1);
- if (scrollOffset == LinearLayoutManager.INVALID_OFFSET) {
- mLayoutManager.scrollToPosition(scrollPosition);
- } else {
- mLayoutManager.scrollToPositionWithOffset(scrollPosition,
- scrollOffset);
- }
-
- } catch (Throwable throwable) {
- throwable.printStackTrace();
- }
-
- }
- });
- mLayoutManager.waitForLayout(2);
- checkForMainThreadException();
- }
-
- private void waitForFirstLayout() throws Throwable {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(mRecyclerView);
- mLayoutManager.waitForLayout(2);
- }
-
public void testRecycleDuringAnimations() throws Throwable {
final AtomicInteger childCount = new AtomicInteger(0);
final TestAdapter adapter = new TestAdapter(300) {
@@ -705,12 +426,6 @@
}
- public void testGetFirstLastChildrenTest() throws Throwable {
- for (Config config : mBaseVariations) {
- getFirstLastChildrenTest(config);
- }
- }
-
public void testDontRecycleChildrenOnDetach() throws Throwable {
setupByConfig(new Config().recycleChildrenOnDetach(false), true);
runTestOnUiThread(new Runnable() {
@@ -740,340 +455,6 @@
});
}
- public void getFirstLastChildrenTest(final Config config) throws Throwable {
- setupByConfig(config, true);
- Runnable viewInBoundsTest = new Runnable() {
- @Override
- public void run() {
- VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
- final String boundsLog = mLayoutManager.getBoundsLog();
- assertEquals(config + ":\nfirst visible child should match traversal result\n"
- + boundsLog, visibleChildren.firstVisiblePosition,
- mLayoutManager.findFirstVisibleItemPosition()
- );
- assertEquals(
- config + ":\nfirst fully visible child should match traversal result\n"
- + boundsLog, visibleChildren.firstFullyVisiblePosition,
- mLayoutManager.findFirstCompletelyVisibleItemPosition()
- );
-
- assertEquals(config + ":\nlast visible child should match traversal result\n"
- + boundsLog, visibleChildren.lastVisiblePosition,
- mLayoutManager.findLastVisibleItemPosition()
- );
- assertEquals(
- config + ":\nlast fully visible child should match traversal result\n"
- + boundsLog, visibleChildren.lastFullyVisiblePosition,
- mLayoutManager.findLastCompletelyVisibleItemPosition()
- );
- }
- };
- runTestOnUiThread(viewInBoundsTest);
- // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
- // case
- final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.smoothScrollToPosition(scrollPosition);
- }
- });
- while (mLayoutManager.isSmoothScrolling() ||
- mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
- runTestOnUiThread(viewInBoundsTest);
- Thread.sleep(400);
- }
- // delete all items
- mLayoutManager.expectLayouts(2);
- mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
- mLayoutManager.waitForLayout(2);
- // test empty case
- runTestOnUiThread(viewInBoundsTest);
- // set a new adapter with huge items to test full bounds check
- mLayoutManager.expectLayouts(1);
- final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
- final TestAdapter newAdapter = new TestAdapter(100) {
- @Override
- public void onBindViewHolder(TestViewHolder holder,
- int position) {
- super.onBindViewHolder(holder, position);
- if (config.mOrientation == HORIZONTAL) {
- holder.itemView.setMinimumWidth(totalSpace + 5);
- } else {
- holder.itemView.setMinimumHeight(totalSpace + 5);
- }
- }
- };
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.setAdapter(newAdapter);
- }
- });
- mLayoutManager.waitForLayout(2);
- runTestOnUiThread(viewInBoundsTest);
- }
-
- public void testSavedState() throws Throwable {
- PostLayoutRunnable[] postLayoutOptions = new PostLayoutRunnable[]{
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- // do nothing
- }
-
- @Override
- public String describe() {
- return "doing nothing";
- }
- },
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- mLayoutManager.expectLayouts(1);
- scrollToPosition(mTestAdapter.getItemCount() * 3 / 4);
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position";
- }
- },
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- mLayoutManager.expectLayouts(1);
- scrollToPositionWithOffset(mTestAdapter.getItemCount() * 1 / 3,
- 50);
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position with positive offset";
- }
- },
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- mLayoutManager.expectLayouts(1);
- scrollToPositionWithOffset(mTestAdapter.getItemCount() * 2 / 3,
- -10); // Some tests break if this value is below the item height.
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position with negative offset";
- }
- }
- };
-
- PostRestoreRunnable[] postRestoreOptions = new PostRestoreRunnable[]{
- new PostRestoreRunnable() {
- @Override
- public String describe() {
- return "Doing nothing";
- }
- },
- new PostRestoreRunnable() {
- @Override
- void onAfterRestore(Config config) throws Throwable {
- // update config as well so that restore assertions will work
- config.mOrientation = 1 - config.mOrientation;
- mLayoutManager.setOrientation(config.mOrientation);
- }
-
- @Override
- boolean shouldLayoutMatch(Config config) {
- return config.mItemCount == 0;
- }
-
- @Override
- public String describe() {
- return "Changing orientation";
- }
- },
- new PostRestoreRunnable() {
- @Override
- void onAfterRestore(Config config) throws Throwable {
- config.mStackFromEnd = !config.mStackFromEnd;
- mLayoutManager.setStackFromEnd(config.mStackFromEnd);
- }
-
- @Override
- boolean shouldLayoutMatch(Config config) {
- return true; //stack from end should not move items on change
- }
-
- @Override
- public String describe() {
- return "Changing stack from end";
- }
- },
- new PostRestoreRunnable() {
- @Override
- void onAfterRestore(Config config) throws Throwable {
- config.mReverseLayout = !config.mReverseLayout;
- mLayoutManager.setReverseLayout(config.mReverseLayout);
- }
-
- @Override
- boolean shouldLayoutMatch(Config config) {
- return config.mItemCount == 0;
- }
-
- @Override
- public String describe() {
- return "Changing reverse layout";
- }
- },
- new PostRestoreRunnable() {
- @Override
- void onAfterRestore(Config config) throws Throwable {
- config.mRecycleChildrenOnDetach = !config.mRecycleChildrenOnDetach;
- mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
- }
-
- @Override
- boolean shouldLayoutMatch(Config config) {
- return true;
- }
-
- @Override
- String describe() {
- return "Change should recycle children";
- }
- },
- new PostRestoreRunnable() {
- int position;
- @Override
- void onAfterRestore(Config config) throws Throwable {
- position = mTestAdapter.getItemCount() / 2;
- mLayoutManager.scrollToPosition(position);
- }
-
- @Override
- boolean shouldLayoutMatch(Config config) {
- return mTestAdapter.getItemCount() == 0;
- }
-
- @Override
- String describe() {
- return "Scroll to position " + position ;
- }
-
- @Override
- void onAfterReLayout(Config config) {
- if (mTestAdapter.getItemCount() > 0) {
- assertEquals(config + ":scrolled view should be last completely visible",
- position,
- config.mStackFromEnd ?
- mLayoutManager.findLastCompletelyVisibleItemPosition()
- : mLayoutManager.findFirstCompletelyVisibleItemPosition());
- }
- }
- }
- };
- boolean[] waitForLayoutOptions = new boolean[]{true, false};
- boolean[] loadDataAfterRestoreOptions = new boolean[]{true, false};
- List<Config> variations = addConfigVariation(mBaseVariations, "mItemCount", 0, 300);
- variations = addConfigVariation(variations, "mRecycleChildrenOnDetach", true);
- for (Config config : variations) {
- for (PostLayoutRunnable postLayoutRunnable : postLayoutOptions) {
- for (boolean waitForLayout : waitForLayoutOptions) {
- for (PostRestoreRunnable postRestoreRunnable : postRestoreOptions) {
- for (boolean loadDataAfterRestore : loadDataAfterRestoreOptions) {
- savedStateTest((Config) config.clone(), waitForLayout,
- loadDataAfterRestore, postLayoutRunnable, postRestoreRunnable);
- removeRecyclerView();
- }
- }
-
- }
- }
- }
- }
-
- public void savedStateTest(Config config, boolean waitForLayout, boolean loadDataAfterRestore,
- PostLayoutRunnable postLayoutOperation, PostRestoreRunnable postRestoreOperation)
- throws Throwable {
- if (DEBUG) {
- Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config " +
- config + " post layout action " + postLayoutOperation.describe() +
- "post restore action " + postRestoreOperation.describe());
- }
- setupByConfig(config, false);
- if (waitForLayout) {
- waitForFirstLayout();
- postLayoutOperation.run();
- }
- Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
- Parcelable savedState = mRecyclerView.onSaveInstanceState();
- // we append a suffix to the parcelable to test out of bounds
- String parcelSuffix = UUID.randomUUID().toString();
- Parcel parcel = Parcel.obtain();
- savedState.writeToParcel(parcel, 0);
- parcel.writeString(parcelSuffix);
- removeRecyclerView();
- // reset for reading
- parcel.setDataPosition(0);
- // re-create
- savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
- removeRecyclerView();
-
- final int itemCount = mTestAdapter.getItemCount();
- if (loadDataAfterRestore) {
- mTestAdapter.deleteAndNotify(0, itemCount);
- }
-
- RecyclerView restored = new RecyclerView(getActivity());
- // this config should be no op.
- mLayoutManager = new WrappedLinearLayoutManager(getActivity(),
- config.mOrientation, config.mReverseLayout);
- mLayoutManager.setStackFromEnd(config.mStackFromEnd);
- restored.setLayoutManager(mLayoutManager);
- // use the same adapter for Rect matching
- restored.setAdapter(mTestAdapter);
- restored.onRestoreInstanceState(savedState);
-
- if (loadDataAfterRestore) {
- mTestAdapter.addAndNotify(itemCount);
- }
-
- postRestoreOperation.onAfterRestore(config);
- assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
- parcel.readString());
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- mLayoutManager.waitForLayout(2);
- // calculate prefix here instead of above to include post restore changes
- final String logPrefix = config + "\npostLayout:" + postLayoutOperation.describe() +
- "\npostRestore:" + postRestoreOperation.describe() + "\n";
- assertEquals(logPrefix + " on saved state, reverse layout should be preserved",
- config.mReverseLayout, mLayoutManager.getReverseLayout());
- assertEquals(logPrefix + " on saved state, orientation should be preserved",
- config.mOrientation, mLayoutManager.getOrientation());
- assertEquals(logPrefix + " on saved state, stack from end should be preserved",
- config.mStackFromEnd, mLayoutManager.getStackFromEnd());
- if (waitForLayout) {
- final boolean strictItemEquality = !loadDataAfterRestore;
- if (postRestoreOperation.shouldLayoutMatch(config)) {
- assertRectSetsEqual(
- logPrefix + ": on restore, previous view positions should be preserved",
- before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
- } else {
- assertRectSetsNotEqual(
- logPrefix
- + ": on restore with changes, previous view positions should NOT "
- + "be preserved",
- before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
- }
- postRestoreOperation.onAfterReLayout(config);
- }
- }
-
public void testScrollAndClear() throws Throwable {
setupByConfig(new Config(), true);
@@ -1093,69 +474,6 @@
}
- void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLayoutManager.scrollToPositionWithOffset(position, offset);
- }
- });
- }
-
- public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
- Map<Item, Rect> after, boolean strictItemEquality) {
- Throwable throwable = null;
- try {
- assertRectSetsEqual("NOT " + message, before, after, strictItemEquality);
- } catch (Throwable t) {
- throwable = t;
- }
- assertNotNull(message + "\ntwo layout should be different", throwable);
- }
-
- public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after) {
- assertRectSetsEqual(message, before, after, true);
- }
-
- public void assertRectSetsEqual(String message, Map<Item, Rect> before, Map<Item, Rect> after,
- boolean strictItemEquality) {
- StringBuilder sb = new StringBuilder();
- sb.append("checking rectangle equality.\n");
- sb.append("before:\n");
- for (Map.Entry<Item, Rect> entry : before.entrySet()) {
- sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
- }
- sb.append("after:\n");
- for (Map.Entry<Item, Rect> entry : after.entrySet()) {
- sb.append(entry.getKey().mAdapterIndex + ":" + entry.getValue()).append("\n");
- }
- message = message + "\n" + sb.toString();
- assertEquals(message + ":\nitem counts should be equal", before.size()
- , after.size());
- for (Map.Entry<Item, Rect> entry : before.entrySet()) {
- final Item beforeItem = entry.getKey();
- Rect afterRect = null;
- if (strictItemEquality) {
- afterRect = after.get(beforeItem);
- assertNotNull(message + ":\nSame item should be visible after simple re-layout",
- afterRect);
- } else {
- for (Map.Entry<Item, Rect> afterEntry : after.entrySet()) {
- final Item afterItem = afterEntry.getKey();
- if (afterItem.mAdapterIndex == beforeItem.mAdapterIndex) {
- afterRect = afterEntry.getValue();
- break;
- }
- }
- assertNotNull(message + ":\nItem with same adapter index should be visible " +
- "after simple re-layout",
- afterRect);
- }
- assertEquals(message + ":\nItem should be laid out at the same coordinates",
- entry.getValue(), afterRect);
- }
- }
-
public void testAccessibilityPositions() throws Throwable {
setupByConfig(new Config(VERTICAL, false, false), true);
final AccessibilityDelegateCompat delegateCompat = mRecyclerView
@@ -1176,453 +494,4 @@
record.getToIndex(),
mLayoutManager.findLastVisibleItemPosition());
}
-
- public void testPrepareForDrop() throws Throwable {
- SelectTargetChildren[] selectors = new SelectTargetChildren[] {
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{1, 0};
- }
- },
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{0, 1};
- }
- },
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{childCount - 1, childCount - 2};
- }
- },
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{childCount - 2, childCount - 1};
- }
- },
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{childCount / 2, childCount / 2 + 1};
- }
- },
- new SelectTargetChildren() {
- @Override
- public int[] selectTargetChildren(int childCount) {
- return new int[]{childCount / 2 + 1, childCount / 2};
- }
- }
- };
- for (SelectTargetChildren selector : selectors) {
- for (Config config : mBaseVariations) {
- prepareForDropTest(config, selector);
- removeRecyclerView();
- }
- }
- }
-
- public void prepareForDropTest(final Config config, SelectTargetChildren selectTargetChildren)
- throws Throwable {
- config.mTestAdapter = new TestAdapter(100) {
- @Override
- public void onBindViewHolder(TestViewHolder holder,
- int position) {
- super.onBindViewHolder(holder, position);
- if (config.mOrientation == HORIZONTAL) {
- final int base = mRecyclerView.getWidth() / 5;
- final int itemRand = holder.mBoundItem.mText.hashCode() % base;
- holder.itemView.setMinimumWidth(base + itemRand);
- } else {
- final int base = mRecyclerView.getHeight() / 5;
- final int itemRand = holder.mBoundItem.mText.hashCode() % base;
- holder.itemView.setMinimumHeight(base + itemRand);
- }
- }
- };
- setupByConfig(config, true);
- mLayoutManager.expectLayouts(1);
- scrollToPosition(mTestAdapter.getItemCount() / 2);
- mLayoutManager.waitForLayout(1);
- int[] positions = selectTargetChildren.selectTargetChildren(mRecyclerView.getChildCount());
- final View fromChild = mLayoutManager.getChildAt(positions[0]);
- final int fromPos = mLayoutManager.getPosition(fromChild);
- final View onChild = mLayoutManager.getChildAt(positions[1]);
- final int toPos = mLayoutManager.getPosition(onChild);
- final OrientationHelper helper = mLayoutManager.mOrientationHelper;
- final int dragCoordinate;
- final boolean towardsHead = toPos < fromPos;
- final int referenceLine;
- if (config.mReverseLayout == towardsHead) {
- referenceLine = helper.getDecoratedEnd(onChild);
- dragCoordinate = referenceLine + 3 -
- helper.getDecoratedMeasurement(fromChild);
- } else {
- referenceLine = helper.getDecoratedStart(onChild);
- dragCoordinate = referenceLine - 3;
- }
- mLayoutManager.expectLayouts(2);
-
- final int x,y;
- if (config.mOrientation == HORIZONTAL) {
- x = dragCoordinate;
- y = fromChild.getTop();
- } else {
- y = dragCoordinate;
- x = fromChild.getLeft();
- }
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mTestAdapter.moveInUIThread(fromPos, toPos);
- mTestAdapter.notifyItemMoved(fromPos, toPos);
- mLayoutManager.prepareForDrop(fromChild, onChild, x, y);
- }
- });
- mLayoutManager.waitForLayout(2);
-
- assertSame(fromChild, mRecyclerView.findViewHolderForAdapterPosition(toPos).itemView);
- // make sure it has the position we wanted
- if (config.mReverseLayout == towardsHead) {
- assertEquals(referenceLine, helper.getDecoratedEnd(fromChild));
- } else {
- assertEquals(referenceLine, helper.getDecoratedStart(fromChild));
- }
- }
-
- static class VisibleChildren {
-
- int firstVisiblePosition = RecyclerView.NO_POSITION;
-
- int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
-
- int lastVisiblePosition = RecyclerView.NO_POSITION;
-
- int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
-
- @Override
- public String toString() {
- return "VisibleChildren{" +
- "firstVisiblePosition=" + firstVisiblePosition +
- ", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
- ", lastVisiblePosition=" + lastVisiblePosition +
- ", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
- '}';
- }
- }
-
- abstract private class PostLayoutRunnable {
-
- abstract void run() throws Throwable;
-
- abstract String describe();
- }
-
- abstract private class PostRestoreRunnable {
-
- void onAfterRestore(Config config) throws Throwable {
- }
-
- abstract String describe();
-
- boolean shouldLayoutMatch(Config config) {
- return true;
- }
-
- void onAfterReLayout(Config config) {
-
- };
- }
-
- class WrappedLinearLayoutManager extends LinearLayoutManager {
-
- CountDownLatch layoutLatch;
-
- OrientationHelper mSecondaryOrientation;
-
- OnLayoutListener mOnLayoutListener;
-
- public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
- super(context, orientation, reverseLayout);
- }
-
- public void expectLayouts(int count) {
- layoutLatch = new CountDownLatch(count);
- }
-
- public void waitForLayout(long timeout) throws InterruptedException {
- waitForLayout(timeout, TimeUnit.SECONDS);
- }
-
- @Override
- public void setOrientation(int orientation) {
- super.setOrientation(orientation);
- mSecondaryOrientation = null;
- }
-
- @Override
- public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
- if (DEBUG) {
- Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
- }
- super.removeAndRecycleView(child, recycler);
- }
-
- @Override
- public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
- if (DEBUG) {
- Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
- }
- super.removeAndRecycleViewAt(index, recycler);
- }
-
- @Override
- void ensureLayoutState() {
- super.ensureLayoutState();
- if (mSecondaryOrientation == null) {
- mSecondaryOrientation = OrientationHelper.createOrientationHelper(this,
- 1 - getOrientation());
- }
- }
-
- private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
- layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
- assertEquals("all expected layouts should be executed at the expected time",
- 0, layoutLatch.getCount());
- getInstrumentation().waitForIdleSync();
- }
-
- @Override
- LayoutState createLayoutState() {
- return new LayoutState() {
- @Override
- View next(RecyclerView.Recycler recycler) {
- final boolean hadMore = hasMore(mRecyclerView.mState);
- final int position = mCurrentPosition;
- View next = super.next(recycler);
- assertEquals("if has more, should return a view", hadMore, next != null);
- assertEquals("position of the returned view must match current position",
- position, RecyclerView.getChildViewHolderInt(next).getLayoutPosition());
- return next;
- }
- };
- }
-
- public String getBoundsLog() {
- StringBuilder sb = new StringBuilder();
- sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
- .append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
- sb.append("\nchildren bounds\n");
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
- .append("[").append("start:").append(
- mOrientationHelper.getDecoratedStart(child)).append(", end:")
- .append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
- }
- return sb.toString();
- }
-
- public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
- RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
- if (itemAnimator == null) {
- return;
- }
- final CountDownLatch latch = new CountDownLatch(1);
- final boolean running = itemAnimator.isRunning(
- new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
- @Override
- public void onAnimationsFinished() {
- latch.countDown();
- }
- }
- );
- if (running) {
- latch.await(timeoutInSeconds, TimeUnit.SECONDS);
- }
- }
-
- public VisibleChildren traverseAndFindVisibleChildren() {
- int childCount = getChildCount();
- final VisibleChildren visibleChildren = new VisibleChildren();
- final int start = mOrientationHelper.getStartAfterPadding();
- final int end = mOrientationHelper.getEndAfterPadding();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- final int childStart = mOrientationHelper.getDecoratedStart(child);
- final int childEnd = mOrientationHelper.getDecoratedEnd(child);
- final boolean fullyVisible = childStart >= start && childEnd <= end;
- final boolean hidden = childEnd <= start || childStart >= end;
- if (hidden) {
- continue;
- }
- final int position = getPosition(child);
- if (fullyVisible) {
- if (position < visibleChildren.firstFullyVisiblePosition ||
- visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
- visibleChildren.firstFullyVisiblePosition = position;
- }
-
- if (position > visibleChildren.lastFullyVisiblePosition) {
- visibleChildren.lastFullyVisiblePosition = position;
- }
- }
-
- if (position < visibleChildren.firstVisiblePosition ||
- visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
- visibleChildren.firstVisiblePosition = position;
- }
-
- if (position > visibleChildren.lastVisiblePosition) {
- visibleChildren.lastVisiblePosition = position;
- }
-
- }
- return visibleChildren;
- }
-
- Rect getViewBounds(View view) {
- if (getOrientation() == HORIZONTAL) {
- return new Rect(
- mOrientationHelper.getDecoratedStart(view),
- mSecondaryOrientation.getDecoratedStart(view),
- mOrientationHelper.getDecoratedEnd(view),
- mSecondaryOrientation.getDecoratedEnd(view));
- } else {
- return new Rect(
- mSecondaryOrientation.getDecoratedStart(view),
- mOrientationHelper.getDecoratedStart(view),
- mSecondaryOrientation.getDecoratedEnd(view),
- mOrientationHelper.getDecoratedEnd(view));
- }
-
- }
-
- Map<Item, Rect> collectChildCoordinates() throws Throwable {
- final Map<Item, Rect> items = new LinkedHashMap<Item, Rect>();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- final int childCount = getChildCount();
- Rect layoutBounds = new Rect(0, 0,
- mLayoutManager.getWidth(), mLayoutManager.getHeight());
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
- .getLayoutParams();
- TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
- Rect childBounds = getViewBounds(child);
- if (new Rect(childBounds).intersect(layoutBounds)) {
- items.put(vh.mBoundItem, childBounds);
- }
- }
- }
- });
- return items;
- }
-
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- try {
- if (mOnLayoutListener != null) {
- mOnLayoutListener.before(recycler, state);
- }
- super.onLayoutChildren(recycler, state);
- if (mOnLayoutListener != null) {
- mOnLayoutListener.after(recycler, state);
- }
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
- layoutLatch.countDown();
- }
-
-
- }
-
- static class OnLayoutListener {
- void before(RecyclerView.Recycler recycler, RecyclerView.State state){}
- void after(RecyclerView.Recycler recycler, RecyclerView.State state){}
- }
-
- static class Config implements Cloneable {
-
- private static final int DEFAULT_ITEM_COUNT = 100;
-
- private boolean mStackFromEnd;
-
- int mOrientation = VERTICAL;
-
- boolean mReverseLayout = false;
-
- boolean mRecycleChildrenOnDetach = false;
-
- int mItemCount = DEFAULT_ITEM_COUNT;
-
- TestAdapter mTestAdapter;
-
- Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
- mOrientation = orientation;
- mReverseLayout = reverseLayout;
- mStackFromEnd = stackFromEnd;
- }
-
- public Config() {
-
- }
-
- Config adapter(TestAdapter adapter) {
- mTestAdapter = adapter;
- return this;
- }
-
- Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
- mRecycleChildrenOnDetach = recycleChildrenOnDetach;
- return this;
- }
-
- Config orientation(int orientation) {
- mOrientation = orientation;
- return this;
- }
-
- Config stackFromBottom(boolean stackFromBottom) {
- mStackFromEnd = stackFromBottom;
- return this;
- }
-
- Config reverseLayout(boolean reverseLayout) {
- mReverseLayout = reverseLayout;
- return this;
- }
-
- public Config itemCount(int itemCount) {
- mItemCount = itemCount;
- return this;
- }
-
- // required by convention
- @Override
- public Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return "Config{" +
- "mStackFromEnd=" + mStackFromEnd +
- ", mOrientation=" + mOrientation +
- ", mReverseLayout=" + mReverseLayout +
- ", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
- ", mItemCount=" + mItemCount +
- '}';
- }
- }
-
- private interface SelectTargetChildren {
- int[] selectTargetChildren(int childCount);
- }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
index 42ad90a..15d6031 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
@@ -20,11 +20,13 @@
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import java.util.concurrent.atomic.AtomicBoolean;
+@MediumTest
public class RecyclerViewAccessibilityTest extends BaseRecyclerViewInstrumentationTest {
public RecyclerViewAccessibilityTest() {
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
index e2d1569..67ddda4 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -18,7 +18,9 @@
import android.graphics.Rect;
import android.os.Debug;
+import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -35,6 +37,7 @@
/**
* Tests for {@link SimpleItemAnimator} API.
*/
+@MediumTest
public class RecyclerViewAnimationsTest extends BaseRecyclerViewAnimationsTest {
final List<TestViewHolder> recycledVHs = new ArrayList<>();
@@ -769,19 +772,7 @@
mLayoutManager.waitForLayout(2);
}
- private static boolean listEquals(List list1, List list2) {
- if (list1.size() != list2.size()) {
- return false;
- }
- for (int i = 0; i < list1.size(); i++) {
- if (!list1.get(i).equals(list2.get(i))) {
- return false;
- }
- }
- return true;
- }
-
- private void testChangeWithPayload(final boolean supportsChangeAnim,
+ private void testChangeWithPayload(final boolean supportsChangeAnim, final boolean canReUse,
Object[][] notifyPayloads, Object[][] expectedPayloadsInOnBind)
throws Throwable {
final List<Object> expectedPayloads = new ArrayList<Object>();
@@ -809,11 +800,18 @@
if (DEBUG) {
Log.d(TAG, " onBind to " + position + "" + holder.toString());
}
- assertTrue(listEquals(payloads, expectedPayloads));
+ assertEquals(expectedPayloads, payloads);
}
};
testAdapter.setHasStableIds(false);
setupBasic(testAdapter.getItemCount(), 0, 10, testAdapter);
+ mRecyclerView.setItemAnimator(new DefaultItemAnimator() {
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return canReUse && super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+ });
((SimpleItemAnimator) mRecyclerView.getItemAnimator()).setSupportsChangeAnimations(
supportsChangeAnim);
@@ -840,7 +838,26 @@
public void testCrossFadingChangeAnimationWithPayload() throws Throwable {
// for crossfading change animation, will receive EMPTY payload in onBindViewHolder
- testChangeWithPayload(true,
+ testChangeWithPayload(true, true,
+ new Object[][]{
+ new Object[]{"abc"},
+ new Object[]{"abc", null, "cdf"},
+ new Object[]{"abc", null},
+ new Object[]{null, "abc"},
+ new Object[]{"abc", "cdf"}
+ },
+ new Object[][]{
+ new Object[]{"abc"},
+ new Object[0],
+ new Object[0],
+ new Object[0],
+ new Object[]{"abc", "cdf"}
+ });
+ }
+
+ public void testCrossFadingChangeAnimationWithPayloadWithoutReuse() throws Throwable {
+ // for crossfading change animation, will receive EMPTY payload in onBindViewHolder
+ testChangeWithPayload(true, false,
new Object[][]{
new Object[]{"abc"},
new Object[]{"abc", null, "cdf"},
@@ -860,7 +877,7 @@
public void testNoChangeAnimationWithPayload() throws Throwable {
// for Change Animation disabled, payload should match the payloads unless
// null payload is fired.
- testChangeWithPayload(false,
+ testChangeWithPayload(false, true,
new Object[][]{
new Object[]{"abc"},
new Object[]{"abc", null, "cdf"},
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
index 917680b..5cc4e36 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import android.os.SystemClock;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
@@ -32,6 +33,7 @@
import java.util.List;
import java.util.UUID;
+@SmallTest
public class RecyclerViewBasicTest extends AndroidTestCase {
RecyclerView mRecyclerView;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
index d6c1f28..08738f3 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -29,6 +29,7 @@
import android.os.SystemClock;
import android.support.v4.view.ViewCompat;
import android.test.TouchUtils;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -56,6 +57,7 @@
import android.support.test.runner.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
+@MediumTest
public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
private static final int FLAG_HORIZONTAL = 1;
private static final int FLAG_VERTICAL = 1 << 1;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
index e09fceb..b380e6c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -18,7 +18,10 @@
package android.support.v7.widget;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.Looper;
import android.os.Parcel;
import android.os.Parcelable;
@@ -26,7 +29,9 @@
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
+import android.util.StateSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
@@ -44,10 +49,15 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import static android.support.v7.widget.LayoutState.*;
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
-import static android.support.v7.widget.StaggeredGridLayoutManager.*;
+import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
+import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
+import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.StaggeredGridLayoutManager.LayoutParams;
+@MediumTest
public class StaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
private static final boolean DEBUG = false;
@@ -79,7 +89,11 @@
}
void setupByConfig(Config config) throws Throwable {
- mAdapter = new GridTestAdapter(config.mItemCount, config.mOrientation);
+ setupByConfig(config, new GridTestAdapter(config.mItemCount, config.mOrientation));
+ }
+
+ void setupByConfig(Config config, GridTestAdapter adapter) throws Throwable {
+ mAdapter = adapter;
mRecyclerView = new RecyclerView(getActivity());
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setHasFixedSize(true);
@@ -308,6 +322,103 @@
}
+ public void testFocusSearchFailureUp() throws Throwable {
+ focusSearchFailure(false);
+ }
+
+ public void testFocusSearchFailureDown() throws Throwable {
+ focusSearchFailure(true);
+ }
+
+ public void focusSearchFailure(boolean scrollDown) throws Throwable {
+ int focusDir = scrollDown ? View.FOCUS_DOWN : View.FOCUS_UP;
+ setupByConfig(new Config(VERTICAL, !scrollDown, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
+ , new GridTestAdapter(31, 1) {
+ RecyclerView mAttachedRv;
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ TestViewHolder testViewHolder = super.onCreateViewHolder(parent, viewType);
+ testViewHolder.itemView.setFocusable(true);
+ testViewHolder.itemView.setFocusableInTouchMode(true);
+ // Good to have colors for debugging
+ StateListDrawable stl = new StateListDrawable();
+ stl.addState(new int[]{android.R.attr.state_focused},
+ new ColorDrawable(Color.RED));
+ stl.addState(StateSet.WILD_CARD, new ColorDrawable(Color.BLUE));
+ testViewHolder.itemView.setBackground(stl);
+ return testViewHolder;
+ }
+
+ @Override
+ public void onAttachedToRecyclerView(RecyclerView recyclerView) {
+ mAttachedRv = recyclerView;
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setMinimumHeight(mAttachedRv.getHeight() / 3);
+ }
+ });
+ /**
+ * 0 1 2
+ * 3 4 5
+ * 6 7 8
+ * 9 10 11
+ * 12 13 14
+ * 15 16 17
+ * 18 18 18
+ * 19
+ * 20 20 20
+ * 21 22
+ * 23 23 23
+ * 24 25 26
+ * 27 28 29
+ * 30
+ */
+ mAdapter.mFullSpanItems.add(18);
+ mAdapter.mFullSpanItems.add(20);
+ mAdapter.mFullSpanItems.add(23);
+ waitFirstLayout();
+ View viewToFocus = mRecyclerView.findViewHolderForAdapterPosition(1).itemView;
+ assertTrue(requestFocus(viewToFocus));
+ getInstrumentation().waitForIdleSync();
+ assertSame(viewToFocus, mRecyclerView.getFocusedChild());
+ int pos = 1;
+ View focusedView = viewToFocus;
+ while (pos < 16) {
+ focusSearchAndWaitForScroll(focusedView, focusDir);
+ focusedView = mRecyclerView.getFocusedChild();
+ assertEquals(pos + 3,
+ mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ pos += 3;
+ }
+ for (int i : new int[]{18, 19, 20, 21, 23, 24}) {
+ focusSearchAndWaitForScroll(focusedView, focusDir);
+ focusedView = mRecyclerView.getFocusedChild();
+ assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ }
+ // now move right
+ focusSearch(focusedView, View.FOCUS_RIGHT);
+ focusedView = mRecyclerView.getFocusedChild();
+ assertEquals(25, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ for (int i : new int[]{28, 30}) {
+ focusSearchAndWaitForScroll(focusedView, focusDir);
+ focusedView = mRecyclerView.getFocusedChild();
+ assertEquals(i, mRecyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ }
+ }
+
+ private void focusSearchAndWaitForScroll(View focused, int dir) throws Throwable {
+ focusSearch(focused, dir);
+ getInstrumentation().waitForIdleSync();
+ while (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ Thread.sleep(100);
+ }
+ }
+
public void testScrollBackAndPreservePositions() throws Throwable {
for (boolean saveRestore : new boolean[]{false, true}) {
@@ -514,9 +625,9 @@
.findLastVisibleItemPositions(
provideArr ? new int[mLayoutManager.getSpanCount()] : null);
assertEquals(config + ":\nfirst visible child should match traversal result\n"
- + "traversed:" + visibleChildren + "\n"
- + "queried:" + queryResult + "\n"
- + boundsLog, visibleChildren, queryResult
+ + "traversed:" + visibleChildren + "\n"
+ + "queried:" + queryResult + "\n"
+ + boundsLog, visibleChildren, queryResult
);
}
};
@@ -927,6 +1038,7 @@
lp.height = mRecyclerView.getHeight() / 3;
}
}
+
@Override
boolean assignRandomSize() {
return false;
@@ -940,7 +1052,7 @@
Rect recyclerViewBounds = getDecoratedRecyclerViewBounds();
// workaround for SGLM's span distribution issue. Right now, it may leave gaps so we
// avoid it by setting its layout params directly
- if(config.mOrientation == HORIZONTAL) {
+ if (config.mOrientation == HORIZONTAL) {
recyclerViewBounds.bottom -= recyclerViewBounds.height() % config.mSpanCount;
} else {
recyclerViewBounds.right -= recyclerViewBounds.width() % config.mSpanCount;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
index c2fac6e..b837e76 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/helper/ItemTouchHelperTest.java
@@ -23,6 +23,7 @@
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.WrappedRecyclerView;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -34,6 +35,7 @@
import static android.support.v7.widget.helper.ItemTouchHelper.*;
+@MediumTest
public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
TestAdapter mAdapter;
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/test/RecyclerViewTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/test/RecyclerViewTest.java
index 6565e6f..4fcc657 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/test/RecyclerViewTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/test/RecyclerViewTest.java
@@ -24,8 +24,10 @@
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
import android.widget.LinearLayout;
+@SmallTest
public class RecyclerViewTest extends ActivityInstrumentationTestCase2<RecyclerViewTestActivity> {
public RecyclerViewTest() {